Node.js y solicitudes intensivas de CPU

215

Comencé a jugar con el servidor HTTP Node.js y realmente me gusta escribir Javascript del lado del servidor, pero algo me impide comenzar a usar Node.js para mi aplicación web.

Entiendo todo el concepto de E / S asíncrona, pero estoy algo preocupado por los casos extremos en los que el código de procedimiento requiere mucha CPU, como la manipulación de imágenes o la clasificación de grandes conjuntos de datos.

Según tengo entendido, el servidor será muy rápido para solicitudes simples de páginas web, como ver una lista de usuarios o ver una publicación de blog. Sin embargo, si quiero escribir un código muy intensivo de CPU (en el back-end de administración, por ejemplo) que genera gráficos o redimensiona miles de imágenes, la solicitud será muy lenta (unos segundos). Dado que este código no es asíncrono, todas las solicitudes que lleguen al servidor durante esos pocos segundos se bloquearán hasta que finalice mi solicitud lenta.

Una sugerencia fue usar Web Workers para tareas intensivas de CPU. Sin embargo, me temo que los trabajadores web harán que sea difícil escribir código limpio, ya que funciona al incluir un archivo JS separado. ¿Qué sucede si el código intensivo de la CPU se encuentra en el método de un objeto? Es un poco malo escribir un archivo JS para cada método que requiere mucha CPU.

Otra sugerencia fue generar un proceso hijo, pero eso hace que el código sea aún menos mantenible.

¿Alguna sugerencia para superar este obstáculo (percibido)? ¿Cómo se escribe código limpio orientado a objetos con Node.js mientras se asegura de que las tareas pesadas de la CPU se ejecuten de forma asíncrona?

Olivier Lalonde
fuente
2
Olivier, usted hizo la misma pregunta que tenía en mente (nuevo en el nodo) y específicamente con respecto al procesamiento de imágenes. En Java puedo usar un ExecutorService de subproceso fijo y pasarle todos los trabajos de cambio de tamaño y esperar a que termine de toda la conexión, en el nodo, no he descubierto cómo reorganizar el trabajo a un módulo externo que limita (vamos a digamos) el número máximo de operaciones simultáneas a 2 a la vez. ¿Encontraste una manera elegante de hacer esto?
Riyad Kalla

Respuestas:

55

¡Lo que necesitas es una cola de tareas! Mover sus tareas de larga duración fuera del servidor web es una BUENA cosa. Mantener cada tarea en un archivo js "separado" promueve la modularidad y la reutilización del código. Te obliga a pensar en cómo estructurar tu programa de una manera que facilite la depuración y el mantenimiento a largo plazo. Otro beneficio de una cola de tareas es que los trabajadores pueden estar escritos en un idioma diferente. Simplemente haga una tarea, haga el trabajo y escriba la respuesta.

algo así https://github.com/resque/resque

Aquí hay un artículo de github sobre por qué lo construyeron http://github.com/blog/542-introducing-resque

Tim
fuente
35
¿Por qué se vincula a las bibliotecas Ruby en una pregunta específicamente basada en el mundo de los nodos?
Jonathan Dumaine
1
@ JonathanDumaine Es una buena implementación de una cola de tareas. Rad el código ruby ​​y reescríbalo en javascript. ¡LUCRO!
Simon Stender Boisen
2
Soy un gran admirador de Gearman por esto, los trabajadores de Gearman no sondean a un servidor de Gearman en busca de nuevos trabajos: los nuevos trabajos se envían instantáneamente a los trabajadores. Muy receptivo
Casey Flynn
1
De hecho, alguien lo ha portado al mundo de nodos: github.com/technoweenie/coffee-resque
FrontierPsycho
@pacerier, ¿por qué dices eso? ¿Qué propones?
luis.espinal
289

Esto es un malentendido de la definición de servidor web: solo debe usarse para "hablar" con los clientes. Las tareas de carga pesada deben delegarse en programas independientes (eso, por supuesto, también se puede escribir en JS).
Probablemente diría que está sucio, pero le aseguro que un proceso de servidor web atascado en cambiar el tamaño de las imágenes es peor (incluso para decir Apache, cuando no bloquea otras consultas). Aún así, puede usar una biblioteca común para evitar la redundancia de código.

EDITAR: se me ocurrió una analogía; La aplicación web debe ser como un restaurante. Tienes camareros (servidor web) y cocineros (trabajadores). Los camareros están en contacto con los clientes y realizan tareas simples como proporcionar un menú o explicar si algún plato es vegetariano. Por otro lado, delegan tareas más difíciles a la cocina. Debido a que los camareros solo hacen cosas simples, responden rápidamente, y los cocineros pueden concentrarse en su trabajo.

Node.js aquí sería un camarero único pero muy talentoso que puede procesar muchas solicitudes a la vez, y Apache sería una pandilla de camareros tontos que solo procesan una solicitud cada uno. Si este mesero de Node.js comenzara a cocinar, sería una catástrofe inmediata. Aún así, la cocina también podría agotar incluso una gran cantidad de camareros Apache, sin mencionar el caos en la cocina y la disminución progresiva de la capacidad de respuesta.

mbq
fuente
66
Bueno, en un entorno donde los servidores web son multiproceso o multiproceso y pueden manejar más de una solicitud concurrente, es muy común pasar un par de segundos en una sola solicitud. La gente ha llegado a esperar eso. Yo diría que el malentendido es que node.js es un servidor web "normal". Usando node.js tienes que ajustar un poco tu modelo de programación, y eso incluye empujar el trabajo "de larga duración" a algún trabajador asincrónico.
Thilo
13
No genere un proceso hijo para cada solicitud (que anula el propósito de node.js). Genera trabajadores desde dentro de tus pesadas solicitudes solamente. O enrute su trabajo de fondo pesado a algo que no sea node.js.
Thilo
47
Buena analogía, mbq!
Lance Fisher
66
Ja, eso me gusta mucho. "Node.js: hacer que las malas prácticas funcionen mal"
ethan
77
@mbq Me gusta la analogía pero podría usar algo de trabajo. El modelo tradicional multiproceso sería una persona que es a la vez camarero y cocinero. Una vez que se toma la orden, esa persona tiene que regresar y cocinar la comida antes de poder manejar otra orden. El modelo node.js tiene los nodos como camareros y los trabajadores web como cocineros. Los camareros se encargan de buscar / resolver las solicitudes, mientras que los trabajadores gestionan las tareas que requieren más tiempo. Si necesita escalar más, simplemente haga que el servidor principal sea un clúster de nodos y revierta las tareas intensivas de la CPU a otros servidores creados para el procesamiento multiproceso.
Evan Plaice
16

No desea que el código intensivo de su CPU se ejecute de forma asíncrona, desea que se ejecute en paralelo . Debe obtener el trabajo de procesamiento del hilo que atiende las solicitudes HTTP. Es la única forma de resolver este problema. Con NodeJS la respuesta es el módulo de clúster, para desovar procesos infantiles para hacer el trabajo pesado. (El nodo AFAIK no tiene ningún concepto de hilos / memoria compartida; es procesos o nada). Tiene dos opciones para estructurar su aplicación. Puede obtener la solución 80/20 generando 8 servidores HTTP y manejando tareas intensivas en cómputo sincrónicamente en los procesos secundarios. Hacer eso es bastante simple. Podrías tomarte una hora para leer sobre eso en ese enlace. De hecho, si simplemente arranca el código de ejemplo en la parte superior de ese enlace, obtendrá el 95% del camino.

La otra forma de estructurar esto es configurar una cola de trabajos y enviar grandes tareas de cómputo a través de la cola. Tenga en cuenta que hay una gran sobrecarga asociada con el IPC para una cola de trabajos, por lo que esto solo es útil cuando las tareas son considerablemente más grandes que la sobrecarga.

Me sorprende que ninguna de estas otras respuestas mencione clúster.

Antecedentes: el código asincrónico es un código que se suspende hasta que algo sucede en otro lugar , momento en el que el código se activa y continúa la ejecución. Un caso muy común en el que algo lento debe suceder en otro lugar es la E / S.

El código asincrónico no es útil si es su procesador el responsable de hacer el trabajo. Ese es precisamente el caso de las tareas de "cálculo intensivo".

Ahora, puede parecer que el código asincrónico es un nicho, pero de hecho es muy común. Simplemente no es útil para calcular tareas intensivas.

Esperar en E / S es un patrón que siempre ocurre en los servidores web, por ejemplo. Cada cliente que se conecta a su servidor obtiene un socket. La mayoría de las veces los enchufes están vacíos. No desea hacer nada hasta que un socket reciba algunos datos, momento en el que desea manejar la solicitud. Bajo el capó, un servidor HTTP como Node está utilizando una biblioteca de eventos (libev) para realizar un seguimiento de los miles de sockets abiertos. El sistema operativo notifica a libev, y luego libev notifica a NodeJS cuando uno de los sockets obtiene datos, y luego NodeJS coloca un evento en la cola de eventos, y su código http se activa en este punto y maneja los eventos uno tras otro. Los eventos no se ponen en la cola hasta que el socket tenga algunos datos, por lo que los eventos nunca esperan datos, ya están ahí para ellos.

Los servidores web basados ​​en eventos de un solo subproceso tienen sentido como paradigma cuando el cuello de botella está esperando en un montón de conexiones de socket en su mayoría vacías y no desea un hilo o proceso completo para cada conexión inactiva y no desea sondear sus 250k sockets para encontrar el siguiente que tenga datos.

masonk
fuente
debería ser la respuesta correcta ... en cuanto a la solución donde genera 8 grupos, necesitaría 8 núcleos, ¿verdad? O equilibrador de carga con múltiples servidores.
Muhammad Umer
También lo que es una buena manera de aprender sobre la segunda solución, configurar una cola. El concepto de cola es bastante simple, pero la parte de mensajería entre procesos y cola es extraña.
Muhammad Umer
Así es. Necesitas llevar el trabajo a otro núcleo, de alguna manera. Para eso, necesitas otro núcleo.
masonk
Re: colas. La respuesta práctica es usar una cola de trabajo. Hay algunos disponibles para el nodo. Nunca he usado ninguno de ellos, así que no puedo hacer una recomendación. La respuesta curiosa es que los procesos de los trabajadores y los procesos de la cola finalmente se van a comunicar a través de sockets.
masonk
7

Un par de enfoques que puedes usar.

Como señala @Tim, puede crear una tarea asincrónica que se encuentre fuera o paralela a su lógica de servicio principal. Depende de sus requisitos exactos, pero incluso cron puede actuar como un mecanismo de cola.

Los WebWorkers pueden funcionar para sus procesos asíncronos, pero actualmente no son compatibles con node.js. Hay un par de extensiones que brindan soporte, por ejemplo: http://github.com/cramforce/node-worker

Usted todavía puede reutilizar módulos y códigos a través del mecanismo estándar "requiere". Solo necesita asegurarse de que el envío inicial al trabajador pase toda la información necesaria para procesar los resultados.

Toby Hede
fuente
0

El uso child_processes una solución. Pero cada proceso secundario generado puede consumir mucha memoria en comparación con Gogoroutines

También puede usar soluciones basadas en colas como kue

neo
fuente