Caché de Nginx: todo nuevo - bien olvidado viejo

En la vida de cada proyecto, llega el momento en que el servidor deja de cumplir con los requisitos de SLA y literalmente comienza a ahogarse en la cantidad de tráfico entrante. Después de eso, comienza el largo proceso de encontrar cuellos de botella, consultas pesadas, índices creados incorrectamente, datos no almacenados en caché o viceversa, datos actualizados con demasiada frecuencia en el caché y otros lados oscuros del proyecto.

Pero, ¿qué hacer cuando su código es "perfecto", todas las solicitudes pesadas se colocan en segundo plano, todo lo posible fue almacenado en caché y el servidor aún no alcanza los indicadores de SLA que necesitamos? Si es posible, por supuesto, puede comprar automóviles nuevos, distribuir parte del tráfico y olvidarse del problema por un tiempo.

Pero si tiene la sensación de que su servidor es capaz de más, o si hay un parámetro mágico que acelera el sitio 100 veces, puede recordar la función nginx incorporada que le permite almacenar en caché las respuestas del backend. Echemos un vistazo a lo que es y cómo puede ayudar a aumentar el número de solicitudes procesadas por el servidor.

¿Qué es el caché Nginx y cómo funciona?


El caché Nginx puede reducir significativamente la cantidad de solicitudes para el backend. Esto se logra almacenando la respuesta HTTP durante un tiempo determinado y al acceder nuevamente al recurso, devolviéndolo de la memoria caché sin enviar la solicitud del backend. El almacenamiento en caché, incluso por un período corto, dará un aumento significativo a la cantidad de solicitudes procesadas por el servidor.

Antes de continuar con la configuración de nginx, debe asegurarse de que esté construido con el módulo "ngx_http_proxy_module", ya que lo configuraremos utilizando este módulo.

Para mayor comodidad, puede transferir la configuración a un archivo separado, por ejemplo, "/etc/nginx/conf.d/cache.conf". Echemos un vistazo a la directiva proxy_cache_path, que le permite configurar los ajustes de almacenamiento en caché.

proxy_cache_path /var/lib/nginx/proxy_cache levels=1:2 keys_zone=proxy_cache:15m max_size=1G; 

"/ Var / lib / nginx / proxy_cache" especifica la ruta de almacenamiento de caché en el servidor. Es en este directorio que nginx guardará los archivos con la respuesta del backend. Al mismo tiempo, nginx no creará de forma independiente un directorio para la memoria caché, debe hacerse cargo de esto usted mismo.

"Niveles = 1: 2": establece el nivel de anidamiento de directorios con un caché. Los niveles de anidación se indican mediante ":", en este caso se crearán 2 directorios, en total se permiten 3 niveles de anidación. Para cada nivel de anidación, los valores del 1 al 2 están disponibles, lo que indica cómo crear el nombre del directorio.

El punto importante es que el nombre del directorio no se elige al azar, sino que se crea en función del nombre del archivo. El nombre del archivo, a su vez, es el resultado de la función md5 de la clave de caché; veremos la clave de caché un poco más tarde.

Veamos en la práctica cómo se construye la ruta al archivo de caché:

 /var/lib/nginx/proxy_cache/2/49/07edcfe6974569ab4da6634ad4e5d492 

El parámetro "Keys_zone = proxy_cache: 15m" establece el nombre de la zona en la memoria compartida, donde se almacenan todas las claves activas y la información sobre ellas. A través de ":" indica el tamaño de la memoria asignada en MB. Según nginx, 1 MB es suficiente para almacenar 8 mil claves.

"Max_size = 1G" define el tamaño máximo de caché para todas las páginas por encima de las cuales nginx se encargará de eliminar los datos menos necesarios.

También es posible controlar la vida útil de los datos en la memoria caché, para esto es suficiente definir el parámetro "inactivo" de la directiva "proxy_cache_path", que es de 10 minutos por defecto. Si durante el tiempo especificado en el parámetro "inactivo" no hubo llamadas a los datos de la memoria caché, estos datos se eliminan incluso si la memoria caché aún no está "agria".

¿Cómo es este caché? Este es en realidad un archivo normal en el servidor, cuyo contenido está escrito:

• clave de caché;
• cabeceras de caché;
• respuesta de contenido desde el backend.

Si todo está claro con los encabezados y la respuesta del backend, entonces hay una serie de preguntas a la "clave de caché". ¿Cómo se construye y cómo se puede administrar?

Para describir la plantilla para construir una clave de caché en nginx, hay una directiva proxy_cache_key, en la que se especifica una cadena como parámetro. Una cadena puede constar de cualquier variable disponible en nginx.

Por ejemplo:

 proxy_cache_key $request_method$host$orig_uri:$cookie_some_cookie:$arg_some_arg; 

El símbolo ":" entre el parámetro de cookie y el parámetro get se usa para evitar colisiones entre claves de caché, puede elegir cualquier otro símbolo de su elección. Por defecto, nginx usa la siguiente línea para generar la clave:

 proxy_cache_key $scheme$proxy_host$request_uri; 

Deben tenerse en cuenta las siguientes directivas que lo ayudarán a administrar su almacenamiento en caché de manera más flexible:

proxy_cache_valid : especifica el tiempo de almacenamiento en caché de la respuesta. Es posible indicar el estado específico de la respuesta, por ejemplo 200, 302, 404, etc., o indicar todo de una vez utilizando la construcción "any". Si solo se especifica el tiempo de almacenamiento en caché, nginx predeterminará solo los estados de 200, 301 y 302.

Un ejemplo:

 proxy_cache_valid 15m; proxy_cache_valid 404 15s; 

En este ejemplo, establecemos la vida útil de la caché en 15 minutos para los estados 200, 301, 302 (nginx los usa de forma predeterminada, ya que no especificamos un estado específico). La siguiente línea establece el tiempo de almacenamiento en caché en 15 segundos, solo para respuestas con un estado de 404.

proxy_cache_lock : esta directiva ayudará a evitar varios pases al backend inmediatamente después de configurar el caché, solo establezca el valor en la posición "on". Todas las demás solicitudes esperarán una respuesta en el caché o un tiempo de espera para bloquear la solicitud a la página. En consecuencia, todos los tiempos de espera se pueden configurar.

proxy_cache_lock_age : le permite establecer un límite de tiempo de espera para una respuesta del servidor, después de lo cual se le enviará la siguiente solicitud después de un conjunto de caché. El valor predeterminado es de 5 segundos.

proxy_cache_lock_timeout : establece el tiempo de espera para el bloqueo, después de lo cual la solicitud se enviará al back-end, pero la respuesta no se almacenará en caché. El valor predeterminado es de 5 segundos.

proxy_cache_use_stale : otra directiva útil que le permite configurar cuándo es posible utilizar un caché obsoleto.

Un ejemplo:

 proxy_cache_use_stale error timeout updating; 

En este caso, utilizará un caché desactualizado en caso de un error de conexión, enviando una solicitud, leyendo una respuesta del servidor, excediendo el límite de espera para enviar una solicitud, leyendo una respuesta del servidor o si los datos en el caché se actualizan en el momento de la solicitud.

proxy_cache_bypass : especifica las condiciones bajo las cuales nginx no recibirá una respuesta de la memoria caché, sino que redirigirá inmediatamente la solicitud al back-end. Si al menos uno de los parámetros no está vacío y no es igual a "0". Un ejemplo:

 proxy_cache_bypass $cookie_nocache $arg_nocache; 

proxy_no_cache : establece la condición bajo la cual nginx no guardará la respuesta del backend en el caché. El principio de funcionamiento es el mismo que el de la directiva proxy_cache_bypass.

Posibles problemas con el almacenamiento en caché de la página


Como se mencionó anteriormente, junto con el almacenamiento en caché de una respuesta HTTP, nginx guarda los encabezados recibidos del back-end. Si su sitio utiliza una sesión, la cookie de sesión también se almacenará en caché. Todos los usuarios que visiten la página que tuvo la suerte de almacenar en caché recibirán sus datos personales almacenados en la sesión.

El próximo desafío que enfrentará es la gestión de almacenamiento en caché. Por supuesto, puede establecer un tiempo de caché insignificante de 2-5 minutos y esto será suficiente en la mayoría de los casos. Pero esto no es aplicable en todas las situaciones, por lo que reinventaremos nuestra bicicleta. Ahora, lo primero es lo primero.

Gestión de preservación de cookies

El almacenamiento en caché en el lado nginx impone algunas restricciones de diseño. Por ejemplo, no podemos usar sesiones en páginas almacenadas en caché, ya que el usuario no llega al backend, otra limitación es la entrega de cookies por parte del backend. Como nginx almacena en caché todos los encabezados, para evitar almacenar la sesión de otra persona en el caché, debemos prohibir la entrega de cookies para las páginas almacenadas en caché. La directiva proxy_ignore_headers nos ayudará con esto. El argumento enumera los encabezados que deben ignorarse desde el backend.

Un ejemplo:

 proxy_ignore_headers "Set-Cookie"; 

Con esta línea, ignoramos la instalación de cookies desde el servidor proxy, es decir, el usuario recibirá una respuesta sin el encabezado "Set-Cookies". En consecuencia, todo lo que el backend intentó escribir en la cookie se ignorará en el lado del cliente, ya que ni siquiera sabrá que estaba destinado a algo. Esta restricción de cookies debe considerarse al desarrollar una aplicación. Por ejemplo, para solicitar autorización, puede desactivar el encendido del encabezado para que el usuario reciba una cookie de sesión.

También debe tener en cuenta la duración de la sesión, se puede ver en el parámetro " session.gc_maxlifetime " de la configuración de php.ini. Imagine que el usuario inició sesión en el sitio y comenzó a ver las noticias, todos los datos ya están en el caché nginx. Después de un tiempo, el usuario se da cuenta de que su autorización ha desaparecido y nuevamente necesita pasar por el proceso de autorización, aunque todo este tiempo estuvo en el sitio, viendo las noticias. Esto sucedió porque en todas sus solicitudes, nginx devolvió el resultado del caché sin enviar una solicitud al backend. Por lo tanto, el servidor decidió que el usuario estaba inactivo y después de un tiempo especificado en " session.gc_maxlifetime " eliminó el archivo de sesión.

Para evitar que esto suceda, podemos emular solicitudes de back-end. Por ejemplo, a través de ajax envíe una solicitud que se garantizará que pase al backend. Para pasar el caché nginx al backend, solo envíe una solicitud POST, también puede usar la regla de la directiva "proxy_cache_bypass", o simplemente deshabilitar el caché para esta página. La solicitud no tiene que devolver algo, puede ser un archivo con una sola línea que inicia la sesión. El propósito de dicha solicitud es extender la vida útil de la sesión mientras el usuario está en el sitio, y nginx entrega concienzudamente los datos en caché a todas sus solicitudes.

Gestión de vaciado de caché

Primero debe determinar los requisitos, qué objetivo estamos tratando de lograr. Digamos que nuestro sitio tiene una sección con una transmisión de texto de eventos deportivos populares. Cuando se carga la página desde el caché, todos los mensajes nuevos vienen en sockets. Para que el usuario vea los mensajes actuales en el momento actual en el primer arranque, en lugar de hace 15 minutos, necesitamos poder borrar de forma independiente el caché nginx en cualquier momento. Al mismo tiempo, nginx puede no estar ubicado en la misma máquina que la aplicación. Además, uno de los requisitos para un restablecimiento será la capacidad de eliminar el caché, en varias páginas a la vez.

Antes de comenzar a escribir su solución, veamos qué ofrece nginx de fábrica. Para restablecer el caché, nginx tiene una directiva especial llamada "proxy_cache_purge", que registra la condición para restablecer el caché. La condición es en realidad una línea normal que, si no está vacía y no es "0", eliminará el caché con la clave que se pasa. Considere un pequeño ejemplo.

 proxy_cache_path /data/nginx/cache keys_zone=cache_zone:10m; map $request_method $purge_method { PURGE 1; default 0; } server { ... location / { proxy_pass http://backend; proxy_cache cache_zone; proxy_cache_key $uri; proxy_cache_purge $purge_method; } } 

Se toma un ejemplo del sitio web oficial de nginx.

La variable $ purge_method es responsable de vaciar el caché, que es una condición para la directiva proxy_cache_purge y se establece en 0 de forma predeterminada. Esto significa que nginx funciona en modo "normal" (guarda las respuestas del backend). Pero si cambia el método de solicitud a "PURGE", en lugar de enviar la solicitud para el backend con guardar la respuesta, la entrada de caché se eliminará utilizando la clave de caché correspondiente. También es posible especificar una máscara de eliminación especificando un "*" al final de la clave de caché. Por lo tanto, no necesitamos conocer la ubicación de la memoria caché en el disco y el principio de formación de claves, nginx asume estas responsabilidades. Pero también hay desventajas en este enfoque.

  • La directiva proxy_cache_purge está disponible como parte de una suscripción comercial.
  • Solo es posible eliminar el caché de forma puntual, o mediante el uso de la máscara del formulario {clave de caché} "*"

Dado que las direcciones de las páginas en caché pueden ser completamente diferentes, sin partes comunes, el enfoque con la máscara "*" y la directiva "proxy_cache_purge" no es adecuado para nosotros. Queda por recordar una pequeña teoría y descubrir tu idea favorita.

Sabemos que el caché nginx es un archivo normal en el servidor. Especificamos independientemente el directorio para almacenar archivos de caché en la directiva "proxy_cache_path", incluso especificamos la lógica de formar la ruta al archivo desde este directorio usando "niveles". Lo único que nos falta es la formación correcta de la clave de almacenamiento en caché. Pero también podemos verlo en la directiva "proxy_cache_key". Ahora todo lo que tenemos que hacer es:

  • formar la ruta completa a la página, exactamente como se especifica en la directiva proxy_cache_key;
  • codificar la cadena resultante en md5;
  • crear directorios anidados utilizando la regla del parámetro "niveles".
  • Y ahora ya tenemos la ruta completa al archivo de caché en el servidor. Ahora todo lo que nos queda es eliminar este mismo archivo. Desde la parte introductoria, sabemos que nginx puede no estar ubicado en la máquina de la aplicación, por lo que debe permitir eliminar varias direcciones a la vez. Nuevamente, describimos el algoritmo:
  • Las rutas generadas a los archivos de caché las escribiremos en el archivo;
  • Escribamos un script bash simple que colocamos en la máquina con la aplicación. Su tarea será conectarse a través de ssh al servidor donde tenemos nginx de caché y eliminar todos los archivos de caché especificados en el archivo generado desde el paso 1;

Pasamos de la teoría a la práctica, escribiremos un pequeño ejemplo que ilustra nuestro algoritmo de trabajo.

Paso 1. Generando un archivo con rutas al caché.

 $urls = [ 'httpGETdomain.ru/news/111/1:2', 'httpGETdomain.ru/news/112/3:4', ]; function to_nginx_cache_path(url) { $nginxHash = md5($url); $firstDir = substr($nginxHash, -1, 1); $secondDir = substr($nginxHash, -3, 2); return "/var/lib/nginx/proxy_cache/$firstDir/$secondDir/$nginxHash"; } //        tmp $filePath = tempnam('tmp', 'nginx_cache_'); //      $fileStream = fopen($filePath, 'a'); foreach ($urls as $url) { //      $cachePath = to_nginx_cache_path($url); //       fwrite($fileStream, $cachePath . PHP_EOL); } //     fclose($fileStream); //  bash       exec("/usr/local/bin/cache_remover $filePath"); 

Tenga en cuenta que la variable $ urls contiene la url de las páginas en caché, ya en el formato proxy_cache_key especificado en la configuración de nginx. Url actúa como una etiqueta para las entidades que se muestran en la página. Por ejemplo, puede crear una tabla regular en la base de datos, donde cada entidad se asignará a una página específica en la que se muestra. Luego, al cambiar cualquier dato, podemos hacer una selección en la tabla y eliminar el caché de todas las páginas que necesitamos.

Paso 2. Conéctese al servidor de caché y elimine los archivos de caché.

 #      ,      FILE_LIST=`cat $1 | tr "\n" " "` #   ssh  SSH=`which ssh` USER="root" #         nginx HOST="10.10.1.0" #   KEY="/var/keys/id_rsa" # SSH ,          $SSH -i ${KEY} ${USER}@${HOST} "rm -f ${FILE_LIST}" #       rm -rf rm -f $1 #   

Los ejemplos anteriores son solo orientativos, no los use en producción. En los ejemplos, se omiten las comprobaciones de los parámetros de entrada y las restricciones de comando. Uno de los problemas que puede encontrar es limitar la longitud del argumento al comando rm. Al realizar pruebas en entornos de desarrollo en pequeños volúmenes, esto puede pasarse por alto fácilmente, y en producción, aparece el error "rm: Lista de argumentos demasiado larga".

Caché de bloque personalizado


Resumamos lo que logramos hacer:

  • reducido la carga en el backend;
  • Aprenda a administrar el almacenamiento en caché
  • aprendió a vaciar el caché en cualquier momento dado.

Pero no todo es tan bueno como podría parecer a primera vista. Ahora, probablemente, si no todos los primeros, entonces precisamente cada segundo sitio tiene una funcionalidad de registro / autorización, después de pasar por el cual querremos mostrar el nombre de usuario en algún lugar del encabezado. El bloque con el nombre es único y debe mostrar el nombre de usuario con el que estamos autorizados. Dado que nginx guarda la respuesta del backend, y en el caso de la página es el contenido html de la página, el bloque con datos personales también se almacenará en caché. Todos los visitantes del sitio verán el nombre del primer usuario que pasó al backend para obtener un conjunto de caché.
Por lo tanto, el backend no debe proporcionar bloques en los que se encuentre la información personal para que esta información no se encuentre en el caché nginx.

Es necesario considerar la carga alternativa de tales partes de la página. Como siempre, esto se puede hacer de muchas maneras, por ejemplo, después de cargar la página, enviar una solicitud ajax y mostrar el cargador en lugar de contenido personal. Otra forma que consideraremos hoy es usar etiquetas ssi. Primero comprendamos qué es SSI, y luego cómo podemos usarlo junto con el caché nginx.

¿Qué es SSI y cómo funciona?


SSI (inclusiones del lado del servidor, inclusiones del lado del servidor) es un conjunto de comandos integrados en una página html que le dice al servidor qué hacer.

Aquí hay una lista de dichos comandos (directivas):

• if / elif / else / endif: el operador de ramificación;
• echo: muestra los valores de las variables;
• include: le permite insertar el contenido de otro archivo en el documento.
Solo se discutirá la última directiva. La directiva include tiene dos parámetros:
• archivo: especifica la ruta al archivo en el servidor. En cuanto al directorio actual;
• virtual: indica la ruta virtual al documento en el servidor.

Estamos interesados ​​en el parámetro "virtual", ya que especificar la ruta completa al archivo en el servidor no siempre es conveniente, o en el caso de una arquitectura distribuida, el archivo en el servidor simplemente no está allí. Directiva de ejemplo:

 <!--#include virtual="/user/personal_news/"--> 

Para que nginx comience a procesar inserciones ssi, debe modificar la ubicación de la siguiente manera:

 location / { ssi on; ... } 

Ahora todas las solicitudes procesadas por la ubicación "/" podrán realizar inserciones ssi.

¿Cómo pasará nuestra solicitud a través de todo este esquema?

  • el cliente solicita la página;
  • Nginx representa la solicitud del backend;
  • el backend le da a la página inserciones ssi;
  • el resultado se almacena en el caché;
  • Nginx "pregunta" los bloques que faltan;
  • La página resultante se envía al cliente.

Como puede ver en los pasos, las construcciones ssi entrarán en la memoria caché nginx, lo que permitirá no almacenar en caché los bloques personales, y se enviará una página html preparada con todas las inserciones al cliente. Aquí nuestra carga funciona, nginx solicita independientemente los bloques de página que faltan. Pero como cualquier otra solución, este enfoque tiene sus ventajas y desventajas. Imagine que hay varios bloques en la página que deberían mostrarse de manera diferente según el usuario, luego cada uno de estos bloques se reemplazará con un inserto ssi. Nginx, como se esperaba, solicitará cada bloque de este tipo desde el backend, es decir, una solicitud del usuario generará inmediatamente varias solicitudes para el backend, lo que no quisiera en absoluto.

Deshacerse de las persistentes solicitudes de backend a través de ssi


Para resolver este problema, el módulo nginx "ngx_http_memcached_module" nos ayudará. El módulo permite recibir valores del servidor memcached. Escribir a través del módulo no funcionará, el servidor de aplicaciones debería encargarse de esto. Considere un pequeño ejemplo de configuración de nginx junto con un módulo:

 server { location /page { set $memcached_key "$uri"; memcached_pass 127.0.0.1:11211; error_page 404 502 504 = @fallback; } location @fallback { proxy_pass http://backend; } } 

En la variable $ memcache_key especificamos la clave por la cual nginx intentará obtener datos de memcache. Los parámetros para conectarse al servidor memcache se establecen en la directiva memcached_pass. La conexión se puede especificar de varias maneras:

• nombre de dominio;

 memcached_pass cache.domain.ru; 

• Dirección IP y puerto;

 memcached_pass localhost:11211; 

• zócalo unix;

 memcached_pass unix:/tmp/memcached.socket; 

• Directiva aguas arriba.

 upstream cachestream { hash $request_uri consistent; server 10.10.1.1:11211; server 10.10.1.2:11211; } location / { ... memcached_pass cachestream; ... } 

Si nginx logró obtener una respuesta del servidor de caché, entonces se la da al cliente. Si no hay datos en el caché, la solicitud se enviará al backend a través de "@fallback". Esta pequeña configuración del módulo memcached en nginx nos ayudará a reducir la cantidad de solicitudes de aprobación para el backend de las inserciones ssi.

Esperamos que este artículo sea útil y pudimos mostrar una de las formas de optimizar la carga en el servidor, considerar los principios básicos para configurar el almacenamiento en caché de nginx y cerrar los problemas que surgen al usarlo.

Source: https://habr.com/ru/post/es428127/


All Articles