Muchos trabajadores de bloqueo versus trabajadores no bloqueantes individuales

9

Suponga que hay un servidor HTTP que acepta conexiones y luego de alguna manera espera que los encabezados se envíen por completo. Me pregunto cuál es la forma más común de implementarlo y cuáles son los pros y los contras del resto. Solo puedo pensar en estos:

Muchos trabajadores de bloqueo son buenos porque:

  • Es más receptivo.
  • Es más fácil introducir nuevas conexiones (los trabajadores las recogen ellos mismos en lugar de esperar que alguien externo las agregue a una lista sincronizada).
  • El uso de la CPU se equilibra automáticamente (sin ningún esfuerzo adicional) a medida que aumenta y disminuye el número de conexiones.
  • Menos uso de CPU (los subprocesos bloqueados se eliminan del bucle de ejecución y no requieren ninguna lógica para saltar entre clientes).

Un solo trabajador sin bloqueo es bueno porque:

  • Utiliza menos memoria.
  • Menos vulnerable a los clientes perezosos (que se conectan al servidor y envían encabezados lentamente o no envían nada).

Como probablemente pueda ver, en mi opinión, múltiples hilos de trabajo parecen una solución un poco mejor en general. El único problema es que es más fácil atacar dicho servidor.

Editar (más investigación): algunos recursos que encontré en la web ( Miles de subprocesos y bloqueo de E / S: la antigua forma de escribir servidores Java es nueva nuevamente (y mucho mejor) por Paul Tyma) sugiere que el enfoque de bloqueo es generalmente mejor, pero Todavía no sé cómo lidiar con conexiones falsas.

PD: No sugiera usar alguna biblioteca o aplicaciones para la tarea. Estoy más interesado en saber cómo funciona o puede funcionar en lugar de hacerlo funcionar.

PSS He dividido la lógica en varias partes y esta solo se encarga de aceptar encabezados HTTP. No los procesa.

Pijusn
fuente
He aquí, hace muchos años, escribí un servidor roscado con bloqueo de E / S, porque era fácil de escribir. Un colega escribió el otro tipo, y funcionó admirablemente. Eran dos formas de la principal oferta de productos en una empresa en la que solía trabajar. Para "clientes perezosos" en el escenario de bloqueo, puede tener un tiempo de espera en la recepción de datos.

Respuestas:

4

No hay bala de plata

En la práctica depende ...

tl; dr - solución fácil, usa nginx ...

Bloqueo

Por ejemplo, Apache por defecto usa un esquema de bloqueo donde el proceso se bifurca para cada conexión. Eso significa que cada conexión necesita su propio espacio de memoria y la gran cantidad de sobrecarga de cambio de contexto aumenta más a medida que aumenta el número de conexiones. Pero el beneficio es que, una vez que se cierra una conexión, el contexto se puede eliminar y cualquier / toda la memoria se puede recuperar fácilmente.

Un enfoque de subprocesos múltiples sería similar porque la sobrecarga de la conmutación de contexto aumenta con el número de conexiones, pero puede ser más eficiente en la memoria en un contexto compartido. El problema con este enfoque es que es difícil administrar la memoria compartida de manera segura. Los enfoques para superar los problemas de sincronización de memoria a menudo incluyen su propia sobrecarga, por ejemplo, el bloqueo puede congelar el hilo principal en cargas intensivas de CPU, y el uso de tipos inmutables agrega una gran cantidad de copias innecesarias de datos.

AFAIK, generalmente se prefiere usar un enfoque multiproceso en un servidor HTTP de bloqueo porque es más seguro / más simple administrar / recuperar la memoria de una manera segura. La recolección de basura se convierte en un problema cuando la recuperación de memoria es tan simple como detener un proceso. Para procesos de larga duración (es decir, un demonio), esa característica es especialmente importante.

Si bien la sobrecarga de cambio de contexto puede parecer insignificante con un pequeño número de trabajadores, las desventajas se vuelven más relevantes a medida que la carga aumenta de cientos a miles de conexiones concurrentes. En el mejor de los casos, el cambio de contexto escala O (n) a la cantidad de trabajadores presentes, pero en la práctica probablemente sea peor.

Donde los servidores que usan el bloqueo pueden no ser la opción ideal para cargas pesadas de E / S, son ideales para el trabajo intensivo de la CPU y el paso de mensajes se mantiene al mínimo.

Sin bloqueo:

Sin bloqueo sería algo como Node.js o nginx. Estos son especialmente conocidos por escalar a un número mucho mayor de conexiones por nodo bajo carga intensiva de IO. Básicamente, una vez que las personas alcanzan el límite superior de lo que los servidores basados ​​en hilos / procesos pueden manejar, comienzan a explorar opciones alternativas. Esto también se conoce como el problema C10K (es decir, la capacidad de manejar 10,000 conexiones concurrentes).

Los servidores asíncronos sin bloqueo generalmente comparten muchas características con un enfoque de subprocesos múltiples con bloqueo en el sentido de que debe tener cuidado para evitar cargas intensivas de CPU porque no desea sobrecargar el subproceso principal. La ventaja es que la sobrecarga incurrida por el cambio de contexto se elimina esencialmente y con solo pasar un mensaje de contexto se convierte en un problema.

Si bien puede que no funcione para muchos protocolos de red, la naturaleza sin estado de HTTP funciona especialmente bien para arquitecturas sin bloqueo. Al usar la combinación de un proxy inverso y varios servidores HTTP sin bloqueo, es posible identificar y enrutar los nodos que experimentan una gran carga.

Incluso en un servidor que solo tiene un nodo, es muy común que la configuración incluya un servidor por núcleo de procesador para maximizar el rendimiento.

Ambos:

El caso de uso 'ideal' sería una combinación de ambos. Un proxy inverso en el frente dedicado a las solicitudes de enrutamiento en la parte superior, luego una mezcla de servidores bloqueantes y no bloqueantes. Sin bloqueo para tareas de E / S como servir contenido estático, contenido de caché, contenido html. Bloqueo para tareas pesadas de CPU como codificación de imágenes / video, transmisión de contenido, procesamiento de números, escritura de bases de datos, etc.

En tu caso:

Si solo está revisando los encabezados pero no está procesando las solicitudes, lo que esencialmente está describiendo es un proxy inverso. En tal caso, definitivamente iría con un enfoque asíncrono.

Sugeriría consultar la documentación del proxy inverso incorporado de nginx .

Aparte:

Leí la redacción del enlace que proporcionó y tiene sentido que async sea una mala elección para su implementación particular. El problema se puede resumir en una declaración.

Descubrí que al cambiar entre clientes, el código para guardar y restaurar valores / estado era difícil

Estaban construyendo una plataforma estatal. En tal caso, un enfoque asíncrono significaría que tendría que guardar / cargar constantemente el estado cada vez que cambia el contexto (es decir, cuando se dispara un evento). Además, en el lado de SMTP están haciendo mucho trabajo intensivo de CPU.

Parece que tenían una comprensión bastante pobre de asíncrono y, como resultado, hicieron muchas suposiciones malas.

Evan Plaice
fuente