BLOG | NGINX

¡Los grupos de subprocesos en NGINX aumentan el rendimiento 9 veces!

NGINX - Parte de F5 - horizontal, negro, tipo RGB
Miniatura de Valentin Bartenev
Valentín Bartenev
Publicado el 19 de junio de 2015

Es bien sabido que NGINX utiliza un enfoque asincrónico e impulsado por eventos para gestionar las conexiones . Esto significa que en lugar de crear otro proceso o hilo dedicado para cada solicitud (como los servidores con una arquitectura tradicional), maneja múltiples conexiones y solicitudes en un solo proceso de trabajo. Para lograr esto, NGINX trabaja con sockets en modo no bloqueante y utiliza métodos eficientes como epoll y kqueue .

Debido a que la cantidad de procesos de peso completo es pequeña (generalmente solo uno por núcleo de CPU) y constante, se consume mucha menos memoria y no se desperdician ciclos de CPU en el cambio de tareas. Las ventajas de este enfoque son bien conocidas a través del propio ejemplo de NGINX. Maneja con éxito millones de solicitudes simultáneas y escala muy bien.

Cada proceso consume memoria adicional, y cada cambio entre ellos consume ciclos de CPU y destruye cachés L.

Pero el enfoque asincrónico basado en eventos todavía tiene un problema. O, como a mí me gusta pensarlo, un “enemigo”. Y el nombre del enemigo es: bloqueo . Desafortunadamente, muchos módulos de terceros utilizan llamadas de bloqueo y los usuarios (y a veces incluso los desarrolladores de los módulos) no son conscientes de los inconvenientes. Las operaciones de bloqueo pueden arruinar el rendimiento de NGINX y deben evitarse a toda costa.

Incluso en el código oficial actual de NGINX no es posible evitar operaciones de bloqueo en todos los casos, y para resolver este problema se implementó el nuevo mecanismo de “grupos de subprocesos” en NGINX versión 1.7.11 y NGINX Plus Release 7 . Más adelante explicaremos qué es y cómo se debe utilizar. Ahora vamos a encontrarnos cara a cara con nuestro enemigo.

Editor: para obtener una descripción general de NGINX Plus R7, consulte Anuncio de NGINX Plus R7 en nuestro blog.

Para obtener información detallada sobre otras características nuevas de NGINX Plus R7, consulte estas publicaciones de blog relacionadas:

 

El problema

Primero, para comprender mejor el problema, algunas palabras sobre cómo funciona NGINX.

En general, NGINX es un manejador de eventos, un controlador que recibe información del kernel sobre todos los eventos que ocurren en las conexiones y luego da comandos al sistema operativo sobre qué hacer. De hecho, NGINX hace todo el trabajo duro orquestando el sistema operativo, mientras que el sistema operativo hace el trabajo rutinario de leer y enviar bytes. Por eso es muy importante que NGINX responda con rapidez y de manera oportuna.

Bucle de eventos NGINX2
El proceso de trabajo escucha y procesa eventos del núcleo.

Los eventos pueden ser tiempos de espera, notificaciones sobre sockets listos para leer o escribir, o notificaciones sobre un error que ocurrió. NGINX recibe un montón de eventos y luego los procesa uno por uno, realizando las acciones necesarias. De este modo, todo el procesamiento se realiza en un bucle simple sobre una cola en un hilo. NGINX saca un evento de la cola y luego reacciona ante él, por ejemplo, escribiendo o leyendo un socket. En la mayoría de los casos, esto es extremadamente rápido (quizás solo requiere unos pocos ciclos de CPU para copiar algunos datos en la memoria) y NGINX procede a través de todos los eventos en la cola en un instante.

Ciclo de procesamiento de la cola de eventos
Todo el procesamiento se realiza en un bucle simple mediante un solo hilo.

¿Pero qué ocurrirá si se ha producido una operación larga y pesada? Todo el ciclo de procesamiento de eventos quedará bloqueado esperando que finalice esta operación.

Entonces, cuando decimos “una operación de bloqueo” nos referimos a cualquier operación que detiene el ciclo de manejo de eventos durante una cantidad de tiempo significativa. Las operaciones pueden resultar bloqueadas por diversos motivos. Por ejemplo, NGINX puede estar ocupado con un procesamiento largo que requiere un uso intensivo de la CPU, o puede tener que esperar para acceder a un recurso (como un disco duro, o un mutex o una llamada a una función de biblioteca que obtiene respuestas de una base de datos de manera sincrónica, etc.). El punto clave es que mientras procesa dichas operaciones, el proceso de trabajo no puede hacer nada más y no puede manejar otros eventos, incluso si hay más recursos del sistema disponibles y algunos eventos en la cola podrían utilizar esos recursos.

Imagínese a un vendedor en una tienda con una larga cola delante de él. El primer chico de la cola pide algo que no está en la tienda sino en el almacén. El vendedor va al almacén para entregar la mercancía. Ahora toda la cola debe esperar un par de horas para esta entrega y todos en la cola están descontentos. ¿Te imaginas la reacción de la gente? El tiempo de espera de cada persona en la cola aumenta con estas horas, pero los artículos que pretenden comprar podrían estar allí mismo, en la tienda.

Todos en la cola deben esperar el pedido de la primera persona.

Casi la misma situación ocurre con NGINX cuando solicita leer un archivo que no está almacenado en la memoria caché, sino que debe leerse desde el disco. Los discos duros son lentos (especialmente los que giran) y, si bien es posible que las demás solicitudes que esperan en la cola no necesiten acceso al disco, de todos modos se ven obligadas a esperar. Como resultado, las latencias aumentan y los recursos del sistema no se utilizan por completo.

Una sola operación de bloqueo puede retrasar todas las operaciones posteriores durante un tiempo considerable.

Algunos sistemas operativos proporcionan una interfaz asincrónica para leer y enviar archivos y NGINX puede usar esta interfaz (consulte la directiva aio ). Un buen ejemplo es FreeBSD. Lamentablemente, no podemos decir lo mismo de Linux. Aunque Linux proporciona un tipo de interfaz asincrónica para leer archivos, tiene un par de desventajas importantes. Uno de ellos son los requisitos de alineación para el acceso a archivos y buffers, pero NGINX lo maneja bien. Pero el segundo problema es peor. La interfaz asincrónica requiere que el indicador O_DIRECT esté configurado en el descriptor de archivo, lo que significa que cualquier acceso al archivo omitirá el caché en la memoria y aumentará la carga en los discos duros. Definitivamente eso no lo hace óptimo para muchos casos.

Para resolver este problema en particular, se introdujeron los grupos de subprocesos en NGINX 1.7.11 y NGINX Plus Release 7.

Ahora veamos qué son los grupos de subprocesos y cómo funcionan.

Grupos de subprocesos

Volvamos a nuestro pobre vendedor que entrega mercancías desde un almacén lejano. Pero se volvió más inteligente (¿o tal vez se volvió más inteligente después de ser golpeado por la multitud de clientes enojados?) y contrató un servicio de entrega. Ahora, cuando alguien pide algo de un almacén lejano, en lugar de ir él mismo al almacén, simplemente deja un pedido a un servicio de entrega y ellos se encargarán del pedido mientras nuestro asistente de ventas seguirá atendiendo a otros clientes. De esta manera, sólo esperan la entrega aquellos clientes cuya mercancía no se encuentra en la tienda, mientras que los demás pueden ser atendidos inmediatamente.

Al pasar un pedido al servicio de entrega se desbloquea la cola.

En términos de NGINX, el grupo de subprocesos realiza las funciones del servicio de entrega. Consiste en una cola de tareas y una serie de subprocesos que manejan la cola. Cuando un proceso de trabajo necesita realizar una operación potencialmente larga, en lugar de procesar la operación por sí mismo, coloca una tarea en la cola del grupo, desde donde puede ser tomada y procesada por cualquier hilo libre.

Los grupos de subprocesos ayudan a aumentar el rendimiento de las aplicação al asignar una operación lenta a un conjunto separado de tareas.
El proceso de trabajo transfiere las operaciones de bloqueo al grupo de subprocesos.

Entonces parece que tenemos otra cola. Bien. Pero en este caso la cola está limitada por un recurso específico. No podemos leer desde una unidad más rápido de lo que la unidad es capaz de producir datos. Ahora al menos la unidad no retrasa el procesamiento de otros eventos y solo esperan las solicitudes que necesitan acceder a los archivos.

La operación de “leer desde el disco” a menudo se utiliza como el ejemplo más común de una operación de bloqueo, pero en realidad la implementación de grupos de subprocesos en NGINX se puede utilizar para cualquier tarea que no sea apropiada para procesar en el ciclo de trabajo principal.

Por el momento, la descarga a grupos de subprocesos se implementa solo para tres operaciones esenciales: la llamada al sistema read() en la mayoría de los sistemas operativos, sendfile() en Linux y aio_write() en Linux, que se utiliza al escribir algunos archivos temporales, como los de caché. Continuaremos probando y evaluando la implementación y podremos transferir otras operaciones a los grupos de subprocesos en futuras versiones si hay un beneficio claro.

Editor: se agregó soporte para la llamada al sistema aio_write() en NGINX 1.9.13 y NGINX Plus R9.

Evaluación comparativa

Es hora de pasar de la teoría a la práctica. Para demostrar el efecto del uso de grupos de subprocesos, vamos a realizar una prueba comparativa sintética que simula la peor combinación de operaciones bloqueantes y no bloqueantes.

Requiere un conjunto de datos que se garantiza que no cabe en la memoria. En una máquina con 48 GB de RAM, hemos generado 256 GB de datos aleatorios en archivos de 4 MB y luego hemos configurado NGINX 1.9.0 para servirlos.

La configuración es bastante sencilla:

procesos_de_trabajo 16;
eventos {
aceptar_mutex desactivado;
}

http {
incluir mime.types;
aplicação de tipo predeterminado /octet-stream;

acceso_log desactivado;
enviar archivo activado;
enviar_archivo_máximo_chunk 512k;

servidor {
escuchar 8000;

ubicación / {
raíz/almacenamiento;
}
}
}

Como puede ver, para lograr un mejor rendimiento se realizaron algunos ajustes: se deshabilitaron el registro y accept_mutex , se habilitó sendfile y se configuró sendfile_max_chunk . La última directiva puede reducir el tiempo máximo empleado en bloquear llamadas sendfile() , ya que NGINX no intentará enviar el archivo completo de una vez, sino que lo hará en fragmentos de 512 KB.

La máquina tiene dos procesadores Intel Xeon E5645 (12 núcleos, 24 subprocesos HT en total) y una interfaz de red de 10 Gbps. El subsistema de disco está representado por cuatro discos duros Western Digital WD1003FBYX dispuestos en una matriz RAID10. Todo este hardware funciona con Ubuntu Server 14.04.1 LTS.

Configuración de generadores de carga y NGINX para el benchmark

Los clientes están representados por dos máquinas con las mismas especificaciones. En una de estas máquinas, wrk crea una carga usando un script Lua. El script solicita archivos de nuestro servidor en un orden aleatorio utilizando 200 conexiones paralelas, y es probable que cada solicitud genere una falla de caché y un bloqueo de lectura del disco. A esta carga la llamaremos carga aleatoria .

En la segunda máquina cliente, ejecutaremos otra copia de wrk que solicitará el mismo archivo varias veces utilizando 50 conexiones paralelas. Dado que se accederá a este archivo con frecuencia, permanecerá en la memoria todo el tiempo. En circunstancias normales, NGINX atendería estas solicitudes muy rápidamente, pero el rendimiento disminuirá si los procesos de trabajo están bloqueados por otras solicitudes. A esta carga la llamaremos carga constante .

El rendimiento se medirá monitoreando el rendimiento de la máquina servidor usando ifstat y obteniendo los resultados de wrk del segundo cliente.

Ahora bien, la primera ejecución sin grupos de subprocesos no nos arroja resultados muy interesantes:

% ifstat -bi eth2 eth2 Kbps de entrada Kbps de salida 5531,24 1,03e+06 4855,23 812922,7 5994,66 1,07e+06 5476,27 981529,3 6353,62 1,12e+06 5166,17 892770,3 5522,81 978540,8 6208,10 985466,7 6370,79 1,12e+06 6123,33 1,07e+06

Como puedes ver, con esta configuración el servidor puede producir aproximadamente 1 Gbps de tráfico en total. En la salida de top , podemos ver que todos los procesos de trabajo pasan la mayor parte del tiempo bloqueando E/S (están en un estado D ):

arriba - 10:40:47 activo 11 días, 1:32, 1 usuario, carga promedio: 49.61, 45.77 62.89Tareas:375 total,2 correr,373 durmiendo,0 interrumpido,0 zombi %Cpu(s):0.0 a nosotros,0.3 sí,0.0 ni,67.7 identificación,31.9 Washington,0.0 Hola,0.0 si,0.0 Miembro de st KiB:49453440 total,49149308 usado,304132 gratis,98780 Intercambio de KiB de buffers:10474236 total,20124 usado,10454112 gratis,46903412 Memoria en caché PID USUARIO PR NI VIRT RES SHR S %CPU %MEM TIEMPO+ COMANDO 4639 vbart 20 0 47180 28152 496 D 0.7 0.1 0:00.17 nginx 4632 vbart 20 0 47180 28196 536 D 0.3 0.1 0:00.11 nginx 4633 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.11 nginx 4635 vbart 20 0 47180 28136 480 D 0.3 0.1 0:00.12 nginx 4636 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00,14 nginx 4637 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00,10 nginx 4638 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00,12 nginx 4640 vbart 20 0 47180 28324 540 D 0,3 0,1 0:00,13 nginx 4641 vbart 20 0 47180 28324 540 D 0,3 0,1 0:00,13 nginx 4642 vbart 20 0 47180 28208 536 D 0,3 0,1 0:00,11 nginx 4643 vbart 20 0 47180 28276 536 D 0,3 0,1 0:00,29 nginx 4644 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00,11 nginx 4645 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00,17 nginx 4646 vbart 20 0 47180 28204 536 D 0,3 0,1 0:00,12 nginx 4647 vbart 20 0 47180 28208 532 D 0,3 0,1 0:00,17 nginx 4631 vbart 20 0 47180 756 252 S 0,0 0,1 0:00,00 nginx 4634 vbart 20 0 47180 28208 536 D 0,0 0,1 0:00,11 nginx< 4648 vbart 20 0 25232 1956 1160 R 0,0 0,0 0:00,08 superior 25921 vbart 20 0 121956 2232 1056 S 0,0 0,0 0:01,97 sshd 25923 vbart 20 0 40304 4160 2208 S 0.0 0.0 0:00.53 zsh

En este caso, el rendimiento está limitado por el subsistema de disco, mientras la CPU está inactiva la mayor parte del tiempo. Los resultados del trabajo también son muy bajos:

Prueba de 1 minuto en ejecución en http://192.0.2.1:8000/1/1/1, 12 subprocesos y 50 conexiones
Estadísticas de subprocesos: Desviación estándar promedio, Desviación estándar máxima +/-
Latencia: 7,42 s, 5,31 s, 24,41 s, 74,73 %
Solicitudes/s: 0,15 0,36 1,00 84,62 %
488 solicitudes en 1,01 min, 2,01 GB de lectura
Solicitudes/s:      8.08
Transferencia/seg:     34,07 MB

Y recuerda, ¡esto es para el archivo que debe servirse desde la memoria! Las latencias excesivamente grandes se deben a que todos los procesos de trabajo están ocupados leyendo archivos de las unidades para atender la carga aleatoria creada por 200 conexiones del primer cliente y no pueden manejar nuestras solicitudes a tiempo.

Es hora de poner en juego nuestros grupos de hilos. Para esto simplemente agregamos la directiva aio threads al bloque de ubicación :

ubicación / { raíz /almacenamiento;
hilos aio;
}

y pedirle a NGINX que recargue su configuración.

Después repetimos la prueba:

% ifstat -bi eth2 eth2 Kbps de entrada Kbps de salida 60915.19 9.51e+06 59978.89 9.51e+06 60122.38 9.51e+06 61179.06 9.51e+06 61798.40 9.51e+06 57072.97 9.50e+06 56072.61 9.51e+06 61279.63 9.51e+06 61243.54 9.51e+06 59632.50 9.50e+06

¡Ahora nuestro servidor produce 9,5 Gbps , en comparación con aproximadamente 1 Gbps sin grupos de subprocesos!

Probablemente podría producir incluso más, pero ya ha alcanzado la capacidad máxima práctica de la red, por lo que en esta prueba NGINX está limitado por la interfaz de red. Los procesos de trabajo pasan la mayor parte del tiempo simplemente durmiendo y esperando nuevos eventos (están en estado S en la parte superior ):

arriba - 10:43:17 activo 11 días, 1:35, 1 usuario, carga promedio: 172.71, 93.84, 77.90Tareas:376 total,1 correr,375 durmiendo,0 interrumpido,0 zombi %Cpu(s):0.2 a nosotros,1.2 sí,0.0 ni,34.8 identificación,61.5 Washington,0.0 Hola,2.3 si,0.0 Miembro de st KiB:49453440 total,49096836 usado,356604 gratis,97236 Intercambio de KiB de buffers:10474236 total,22860 usado,10451376 gratis,46836580 Memoria en caché PID USUARIO PR NI VIRT RES SHR S %CPU %MEM TIEMPO+ COMANDO 4654 vbart 20 0 309708 28844 596 S 9.0 0.1 0:08.65 nginx 4660 vbart 20 0 309748 28920 596 S 6.6 0.1 0:14.82 nginx 4658 vbart 20 0 309452 28424 520 S 4.3 0.1 0:01.40 nginx 4663 vbart 20 0 309452 28476 572 S 4.3 0.1 0:01.32 nginx 4667 vbart 20 0 309584 28712 588 S 3.7 0.1 0:05.19 nginx 4656 vbart 20 0 309452 28476 572 S 3.3 0.1 0:01.84 nginx 4664 vbart 20 0 309452 28428 524 S 3.3 0.1 0:01.29 nginx 4652 vbart 20 0 309452 28476 572 S 3.0 0.1 0:01.46 nginx 4662 vbart 20 0 309552 28700 596 S 2.7 0.1 0:05.92 nginx 4661 vbart 20 0 309464 28636 596 S 2,3 0,1 0:01,59 nginx 4653 vbart 20 0 309452 28476 572 S 1,7 0,1 0:01,70 nginx 4666 vbart 20 0 309452 28428 524 S 1,3 0,1 0:01,63 nginx 4657 vbart 20 0 309584 28696 592 S 1,0 0,1 0:00,64 nginx 4655 vbart 20 0 30958 28476 572 S 0,7 0,1 0:02.81 nginx 4659 vbart 20 0 309452 28468 564 S 0.3 0.1 0:01.20 nginx 4665 vbart 20 0 309452 28476 572 S 0.3 0.1 0:00.71 nginx 5180 vbart 20 0 25232 1952 1156 R 0.0 0.0 0:00.45 top 4651 vbart 20 0 20032 752 252 S 0.0 0.0 0:00.00 nginx 25921 vbart 20 0 121956 2176 1000 S 0.0 0.0 0:01.98 sshd 25923 vbart 20 0 40304 3840 2208 S 0.0 0.0 0:00.54 zsh

Todavía quedan muchos recursos de CPU.

Los resultados del trabajo :

Prueba de 1 minuto en ejecución en http://192.0.2.1:8000/1/1/1, 12 subprocesos y 50 conexiones
Estadísticas de subprocesos: Desviación estándar promedio, Desviación estándar máxima +/-
Latencia: 226,32 ms, 392,76 ms, 1,72 s, 93,48 %
Solicitudes/s: 20,02 10,84 59,00 65,91 %
15 045 solicitudes en 1 minuto, 58,86 GB de lectura
Solicitudes/s:    250,57
Transferencia/seg:      0.98 GB

El tiempo promedio para servir un archivo de 4 MB se ha reducido de 7,42 segundos a 226,32 milisegundos (33 veces menos) y el número de solicitudes por segundo ha aumentado 31 veces (250 frente a 8).

La explicación es que nuestras solicitudes ya no esperan en la cola de eventos para ser procesadas mientras los procesos de trabajo están bloqueados en la lectura, sino que son manejadas por subprocesos libres. Mientras el subsistema de disco esté haciendo su trabajo lo mejor que pueda para atender nuestra carga aleatoria desde la primera máquina cliente, NGINX usa el resto de los recursos de la CPU y la capacidad de la red para atender las solicitudes del segundo cliente desde la memoria.

Aún no es una bala de plata

Después de todos nuestros temores acerca del bloqueo de operaciones y algunos resultados emocionantes, probablemente la mayoría de ustedes ya van a configurar grupos de subprocesos en sus servidores. No tengas prisa

Lo cierto es que, afortunadamente, la mayoría de operaciones de lectura y envío de archivos no se aplican a discos duros lentos. Si tiene suficiente RAM para almacenar el conjunto de datos, entonces un sistema operativo será lo suficientemente inteligente como para almacenar en caché los archivos utilizados con frecuencia en un denominado "caché de página".

El caché de páginas funciona bastante bien y permite que NGINX demuestre un gran rendimiento en casi todos los casos de uso comunes. La lectura desde el caché de páginas es bastante rápida y nadie puede llamar a estas operaciones “bloqueantes”. Por otro lado, la descarga a un grupo de subprocesos tiene cierta sobrecarga.

Entonces, si tiene una cantidad razonable de RAM y su conjunto de datos de trabajo no es muy grande, entonces NGINX ya funciona de la manera más óptima sin usar grupos de subprocesos.

La descarga de operaciones de lectura al grupo de subprocesos es una técnica aplicable a tareas muy específicas. Es más útil cuando el volumen de contenido solicitado con frecuencia no cabe en el caché de la máquina virtual del sistema operativo. Este podría ser el caso, por ejemplo, de un servidor multimedia de transmisión basado en NGINX con mucha carga. Esta es la situación que hemos simulado en nuestro benchmark.

Sería fantástico si pudiéramos mejorar la descarga de operaciones de lectura en grupos de subprocesos. Todo lo que necesitamos es una forma eficiente de saber si los datos del archivo necesarios están en la memoria o no, y sólo en el último caso la operación de lectura debe descargarse en un hilo separado.

Volviendo a nuestra analogía de ventas, actualmente el vendedor no puede saber si el artículo solicitado está en la tienda y debe o bien pasar siempre todos los pedidos al servicio de entrega o bien gestionarlos él mismo.

El culpable es que los sistemas operativos carecen de esta característica. Los primeros intentos de agregarlo a Linux como llamada al sistema fincore() fueron en 2010, pero eso no sucedió. Más tarde hubo una serie de intentos de implementarlo como una nueva llamada al sistema preadv2() con el indicador RWF_NONBLOCK (consulte Operaciones de lectura de archivos con búfer sin bloqueo y Operaciones de lectura con búfer asincrónicas en LWN.net para obtener más detalles). El destino de todos estos parches aún no está claro. Lo triste aquí es que parece que la principal razón por la que estos parches no han sido aceptados aún en el kernel es el continuo bikeshedding .

Por otro lado, los usuarios de FreeBSD no necesitan preocuparse en absoluto. FreeBSD ya dispone de una interfaz asincrónica suficientemente buena para leer archivos, que debería utilizar en lugar de grupos de subprocesos.

Configuración de grupos de subprocesos

Entonces, si está seguro de que puede obtener algún beneficio al usar grupos de subprocesos en su caso de uso, entonces es momento de profundizar en la configuración.

La configuración es bastante fácil y flexible. Lo primero que debes tener es NGINX versión 1.7.11 o posterior, compilada con el argumento --with-threads en el comando de configuración . Los usuarios de NGINX Plus necesitan la versión 7 o posterior. En el caso más simple, la configuración parece muy sencilla. Todo lo que necesitas es incluir la directiva aio threads en el contexto apropiado:

# en los hilos de contexto 'http', 'servidor' o 'ubicación';

Esta es la configuración mínima posible de los grupos de subprocesos. De hecho, es una versión corta de la siguiente configuración:

# en el contexto 'principal', el valor predeterminado de threads_pool es 32, el valor máximo de cola es 65536;

# en el contexto 'http', 'servidor' o 'ubicación'
aio threads=default;

Define un grupo de subprocesos denominado predeterminado con 32 subprocesos de trabajo y una longitud máxima para la cola de tareas de 65536 tareas. Si la cola de tareas está sobrecargada, NGINX rechaza la solicitud y registra este error:

Desbordamiento de la cola del grupo de subprocesos " NOMBRE ": N tareas en espera

El error significa que es posible que los subprocesos no puedan manejar el trabajo tan rápido como se agrega a la cola. Puede intentar aumentar el tamaño máximo de la cola, pero si eso no ayuda, indica que su sistema no es capaz de atender tantas solicitudes.

Como ya habrás notado, con la directiva thread_pool puedes configurar la cantidad de subprocesos, la longitud máxima de la cola y el nombre de un grupo de subprocesos específico. Esto último implica que puedes configurar varios grupos de subprocesos independientes y usarlos en diferentes lugares de tu archivo de configuración para cumplir distintos propósitos:

# en el contexto 'principal'
grupo de subprocesos un subproceso=128 cola_máxima=0;
grupo de subprocesos dos subprocesos=32;

http {
servidor {
ubicación /uno {
aio subprocesos=uno;
}

ubicación /dos {
aio subprocesos=dos;
}

}
# ...
}

Si no se especifica el parámetro max_queue , se utiliza el valor 65536 de forma predeterminada. Como se muestra, es posible establecer max_queue en cero. En este caso, el grupo de subprocesos solo podrá manejar tantas tareas como subprocesos haya configurados; ninguna tarea esperará en la cola.

Ahora imaginemos que tiene un servidor con tres discos duros y desea que este servidor funcione como un “proxy de almacenamiento en caché” que almacena en caché todas las respuestas de sus backends. La cantidad esperada de datos almacenados en caché excede ampliamente la RAM disponible. En realidad, es un nodo de almacenamiento en caché para su CDN personal. Por supuesto, en este caso lo más importante es conseguir el máximo rendimiento de las unidades.

Una de sus opciones es configurar una matriz RAID. Este enfoque tiene sus ventajas y desventajas. Ahora con NGINX puedes tomar otra:

# Suponemos que cada disco duro está montado en uno de estos directorios: # /mnt/disk1, /mnt/disk2 o /mnt/disk3

# en el contexto principal
grupo de subprocesos grupo_1 subprocesos=16;
grupo de subprocesos grupo_2 subprocesos=16;
grupo de subprocesos grupo_3 subprocesos=16;

http {
ruta_caché_proxy /mnt/disk1 niveles=1:2 zona_claves=caché_1:256m tamaño_máximo=1024G 
ruta_temp_use=off;
ruta_caché_proxy /mnt/disk2 niveles=1:2 zona_claves=caché_2:256m tamaño_máximo=1024G 
ruta_temp_use=off; ruta_caché_proxy /mnt/disk3 niveles=1:2 zona_claves=caché_3:256m tamaño_máximo=1024G
ruta_temp_uso=desactivada;

clientes_divididos $uri_solicitud $disco {
33.3% 1;
33.3% 2;
* 3;
}

servidor {
# ...
ubicación / {
contraseña_proxy http://backend;
clave_caché_proxy $uri_solicitud;
caché_proxy cache_$disco;
hilos_aio=pool_$disco;
enviar_archivo activado;
}
}
}

En esta configuración, las directivas thread_pool definen un grupo de subprocesos dedicado e independiente para cada disco, y las directivas proxy_cache_path definen un caché dedicado e independiente en cada disco.

El módulo split_clients se utiliza para equilibrar la carga entre los cachés (y como resultado entre los discos), lo que se adapta perfectamente a esta tarea.

El parámetro use_temp_path=off de la directiva proxy_cache_path indica a NGINX que guarde los archivos temporales en los mismos directorios donde se encuentran los datos de caché correspondientes. Es necesario evitar copiar datos de respuesta entre los discos duros al actualizar nuestros cachés.

Todo esto en conjunto nos permite obtener el máximo rendimiento del subsistema de disco actual, porque NGINX a través de grupos de subprocesos separados interactúa con las unidades en paralelo e independientemente. Cada una de las unidades es atendida por 16 subprocesos independientes con una cola de tareas dedicada para leer y enviar archivos.

Apuesto a que a sus clientes les gusta este enfoque personalizado. Asegúrate de que a tus discos duros también les guste.

Este ejemplo es una buena demostración de la flexibilidad con la que NGINX puede ajustarse específicamente a su hardware. Es como si estuvieras dando instrucciones a NGINX sobre la mejor manera de interactuar con la máquina y su conjunto de datos. Y al ajustar NGINX en el espacio del usuario, puede asegurarse de que su software, sistema operativo y hardware trabajen juntos en el modo más óptimo para utilizar todos los recursos del sistema de la manera más efectiva posible.

CONCLUSIÓN

En resumen, los grupos de subprocesos son una gran característica que lleva a NGINX a nuevos niveles de rendimiento al eliminar uno de sus enemigos más conocidos y de larga data: el bloqueo, especialmente cuando hablamos de volúmenes de contenido realmente grandes.

Y aún hay más por venir. Como se mencionó anteriormente, esta nueva interfaz potencialmente permite descargar cualquier operación larga y bloqueante sin ninguna pérdida de rendimiento. NGINX abre nuevos horizontes en términos de tener una gran cantidad de nuevos módulos y funcionalidades. Muchas bibliotecas populares aún no ofrecen una interfaz asincrónica sin bloqueo, lo que las hacía incompatibles con NGINX. Podemos invertir mucho tiempo y recursos en desarrollar nuestro propio prototipo sin bloqueo de alguna biblioteca, pero ¿siempre valdrá la pena el esfuerzo? Ahora, con grupos de subprocesos integrados, es posible utilizar dichas bibliotecas con relativa facilidad, creando dichos módulos sin impacto en el rendimiento.

Manténganse al tanto.

Pruebe usted mismo los grupos de subprocesos en NGINX Plus: comience hoy su prueba gratuita de 30 días o contáctenos para analizar sus casos de uso .


"Esta publicación de blog puede hacer referencia a productos que ya no están disponibles o que ya no reciben soporte. Para obtener la información más actualizada sobre los productos y soluciones F5 NGINX disponibles, explore nuestra familia de productos NGINX . NGINX ahora es parte de F5. Todos los enlaces anteriores de NGINX.com redirigirán a contenido similar de NGINX en F5.com.