¿Por qué aparecieron los servidores web asíncronos?

Hola a todos. En contacto Vladislav Rodin. Actualmente, soy el jefe del curso High Load Architect en OTUS, y también doy cursos sobre arquitectura de software.

Además de la enseñanza, como puede ver, he estado escribiendo para un material de copyright del blog OTUS Habré y el artículo de hoy quiero coincidir con el inicio del curso «Administrador de Linux" , que ahora está abierto.





Introducción


¿Por qué la aplicación web se ralentiza y no retiene la carga? Los desarrolladores, que fueron los primeros en encontrar esa pregunta y realizaron investigaciones en algunos sistemas, llegaron a la decepcionante conclusión de que optimizar una lógica de negocios por sí sola no sería suficiente. La respuesta a esta pregunta se encuentra en un nivel inferior, en el nivel del sistema operativo. Para que su aplicación sostenga la carga, es necesario revisar su concepto arquitectónico de tal manera que funcione efectivamente a este nivel. Esto condujo a la aparición de servidores web asincrónicos.

Desafortunadamente, no pude encontrar un solo material que me permita restaurar todas las relaciones causa-efecto en la evolución de los servidores web a la vez. Entonces surgió la idea de escribir este artículo, que, espero, se convertirá en tal material.

Características del sistema operativo Linux


Antes de hablar sobre los modelos de servidores web, me permito recordar algunas características de los procesos y subprocesos en Linux. Lo necesitaremos al analizar las ventajas y desventajas de los modelos anteriores.

Cambio de contexto


Lo más probable es que cualquier usuario que sepa que solo se puede ejecutar un programa en un núcleo de procesador a la vez preguntará: "¿Por qué se pueden iniciar 20 programas en mi procesador de 4 núcleos a la vez?".

De hecho, esto se debe al hecho de que tiene lugar la multitarea preventiva . El sistema operativo asigna una cierta cantidad de tiempo (~ 50 μs) y coloca el programa para que se ejecute en el núcleo durante este tiempo. Después de que se agote el tiempo, el cambio de contexto se interrumpe y cambia. Es decir, el sistema operativo simplemente ejecuta el siguiente programa. Dado que el cambio ocurre con frecuencia, tenemos la impresión de que todos los programas funcionan simultáneamente. Preste atención a la alta frecuencia de conmutación, esto será importante para la presentación posterior.

El cambio de contexto se mencionó anteriormente. Que incluye? Al cambiar el contexto, es necesario guardar los registros del procesador, borrar su canal de instrucciones, guardar las regiones de memoria asignadas al proceso. En general, la operación es bastante costosa. Tarda ~ 0.5 μs, mientras que la ejecución de una línea simple de código es ~ 1 ns. Además, con un aumento en el número de procesos por núcleo de procesador, aumentará la sobrecarga para el cambio de contexto.

Modelos de servidor web


Actualmente, existen los siguientes modelos de servidor web:

  • trabajador
  • prefork
  • asincrónico
  • conjunto


Discutamos cada uno de ellos por separado.

Trabajador y prefork


Históricamente, con estos modelos, todo comenzó. La esencia es muy simple : un cliente viene a nosotros, seleccionamos un controlador separado para él, que procesa el cliente entrante desde el principio hasta el final. Un controlador puede ser un proceso (prefork) o un subproceso (trabajador). Un ejemplo de dicho servidor web es el conocido Apache.

Haré una reserva de inmediato: crear un nuevo controlador para cada cliente es costoso. Primero, con un número constante de núcleos, un aumento en el número de procesadores conduce a un aumento en la latencia (debido a cambios de contexto). En segundo lugar, la cantidad de memoria requerida crece linealmente con el aumento de clientes, porque incluso si usa hilos de intercambio de memoria, cada hilo tiene su propia pila. Por lo tanto, el número de clientes procesados ​​simultáneamente es limitado.El tamaño de la agrupación, que, a su vez, depende de la cantidad de núcleos de procesador. El problema se resuelve utilizando métodos de escala vertical.

Otro inconveniente fundamental de tales servidores es el uso no óptimo de los recursos. Los procesos (o subprocesos) están inactivos la mayor parte del tiempo . Imagine la siguiente situación: durante el procesamiento del cliente, es necesario tomar algunos datos del disco duro, hacer una solicitud a la base de datos o escribir algo en la red. Dado que leer desde un disco duro en Linux es una operación de bloqueo , el proceso (o subproceso) se bloqueará esperando una respuesta, pero aún participará en la asignación del tiempo del procesador.

Trabajador vs prefork


Trabajador y prefork tienen bastantes diferencias fundamentales. Las secuencias son algo más económicas en memoria porque lo comparten. Por la misma razón, un cambio de contexto entre ellos es más fácil que entre procesos. Sin embargo, en el caso de un trabajador, el código se vuelve multiproceso, porque los hilos deben estar sincronizados. Como resultado, obtenemos todos los "encantos" del código multiproceso: se hace más difícil escribirlo, leerlo, probarlo y depurarlo.

Modelo asincrónico


Por lo tanto, trabajador y prefork no permiten procesar una gran cantidad de clientes al mismo tiempo debido al tamaño limitado de la agrupación, y tampoco utilizan de manera óptima los recursos debido a la conmutación de contexto y el bloqueo de llamadas al sistema. Como puede ver, el problema es multihilo y un programador pesado del sistema operativo. Esto lleva a la siguiente idea: procesemos clientes en un solo hilo, pero dejemos que se cargue al 100%.

Dichos servidores se basan en un bucle de eventos y una plantilla de reactor ( máquina de eventos ). El código del cliente, al iniciar una operación de E / S, registra una devolución de llamadaen una cola de prioridad (la prioridad es el tiempo de preparación). El bucle de eventos sondea los descriptores que esperan E / S y luego actualiza la prioridad (si está disponible). Además, el bucle de eventos extrae eventos de la cola de prioridad, lo que hace que las devoluciones de llamada devuelvan el control al bucle de eventos al final.

Este modelo le permite manejar una gran cantidad de clientes, evitando gastos generales para cambiar el contexto . Este modelo no es perfecto y tiene varias desventajas. En primer lugar, no se consume más de un núcleo de procesador , porque solo hay un proceso. Esto se trata utilizando un modelo combinado, que se discutirá a continuación. En segundo lugar, los clientes están conectados por este proceso.. El código debe escribirse con cuidado. Pérdidas de memoria, errores conducen al hecho de que todos los clientes se caen a la vez. Además, este proceso no debe ser bloqueado por nada, la devolución de llamada no debe consistir en resolver algunas tareas difíciles, porque todos los clientes serán bloqueados. En tercer lugar, el código asincrónico es mucho más complicado . Es necesario registrar una devolución de llamada adicional para que los datos no lleguen, para resolver el problema de cómo bifurcar correctamente, etc.

Modelo combinado


Este modelo se usa en servidores reales. Este modelo tiene un grupo de procesos, cada uno de los cuales tiene un grupo de subprocesos, cada uno de los cuales, a su vez, utiliza un modelo de procesamiento basado en la entrada-salida asíncrona. Nginx presenta un modelo combinado.

Conclusión


Por lo tanto, volviendo a los conceptos básicos del sistema operativo, examinamos las diferencias conceptuales entre los modelos de servidor web utilizados en Apache y Nginx. Cada uno de ellos tiene sus propias ventajas y desventajas, por lo que su combinación a menudo se usa en la producción.

La idea del procesamiento asincrónico ha evolucionado aún más: a nivel de las plataformas de lenguaje, surgieron los conceptos de hilos / fibras / gorutinas verdes, que le permiten "esconder bajo el capó" la asincronía de entrada y salida, dejando al desarrollador feliz con un hermoso código sincrónico. Sin embargo, este concepto merece un artículo separado.



Aprende más sobre el curso.



All Articles