Entiendo que Node.js usa un solo subproceso y un bucle de eventos para procesar solicitudes que solo procesan una a la vez (que no es de bloqueo). Pero aún así, cómo funciona eso, digamos 10,000 solicitudes simultáneas. ¿El bucle de eventos procesará todas las solicitudes? ¿No tomaría eso demasiado tiempo?
No puedo entender (todavía) cómo puede ser más rápido que un servidor web multiproceso. Entiendo que el servidor web multiproceso será más costoso en recursos (memoria, CPU), pero ¿no sería aún más rápido? Probablemente estoy equivocado; explique cómo este subproceso único es más rápido en muchas solicitudes y qué hace normalmente (en un nivel alto) cuando atiende muchas solicitudes como 10,000.
Y también, ¿se escalará bien ese hilo único con esa gran cantidad? Tenga en cuenta que estoy empezando a aprender Node.js.
Respuestas:
Si tiene que hacer esta pregunta, probablemente no esté familiarizado con lo que hacen la mayoría de las aplicaciones / servicios web. Probablemente esté pensando que todo el software hace esto:
Sin embargo, no es así como funcionan las aplicaciones web, o incluso cualquier aplicación con una base de datos como back-end. Las aplicaciones web hacen esto:
En este escenario, el software pasa la mayor parte de su tiempo de ejecución usando un 0% de tiempo de CPU esperando que la base de datos regrese.
Aplicación de red multiproceso:
Las aplicaciones de red multiproceso manejan la carga de trabajo anterior de esta manera:
Entonces, el hilo pasa la mayor parte de su tiempo usando 0% de CPU esperando que la base de datos devuelva datos. Al hacerlo, tuvieron que asignar la memoria requerida para un subproceso que incluye una pila de programas completamente separada para cada subproceso, etc. Además, tendrían que iniciar un subproceso que, aunque no es tan costoso como iniciar un proceso completo, todavía no es exactamente barato.
Bucle de evento de subproceso único
Dado que pasamos la mayor parte de nuestro tiempo usando 0% de CPU, ¿por qué no ejecutamos un código cuando no usamos CPU? De esa manera, cada solicitud seguirá obteniendo la misma cantidad de tiempo de CPU que las aplicaciones multiproceso, pero no necesitamos iniciar un subproceso. Entonces hacemos esto:
En la práctica, ambos enfoques devuelven datos con aproximadamente la misma latencia, ya que es el tiempo de respuesta de la base de datos el que domina el procesamiento.
La principal ventaja aquí es que no necesitamos generar un nuevo hilo, por lo que no necesitamos hacer mucho, mucho malloc, lo que nos ralentizaría.
Magia, hilos invisibles
Lo aparentemente misterioso es cómo ambos enfoques anteriores logran ejecutar la carga de trabajo en "paralelo". La respuesta es que la base de datos está enhebrada. Por lo tanto, nuestra aplicación de subproceso único está aprovechando el comportamiento de subprocesos múltiples de otro proceso: la base de datos.
Donde el enfoque de un solo hilo falla
Una aplicación de subproceso único falla mucho si necesita hacer muchos cálculos de CPU antes de devolver los datos. Ahora, no me refiero a un bucle para procesar el resultado de la base de datos. Eso sigue siendo principalmente O (n). Lo que quiero decir es cosas como hacer la transformación de Fourier (codificación mp3, por ejemplo), el trazado de rayos (representación 3D), etc.
Otro escollo de las aplicaciones de subproceso único es que solo utilizará un solo núcleo de CPU. Entonces, si tiene un servidor de cuatro núcleos (no es raro hoy en día), no está utilizando los otros 3 núcleos.
Donde el enfoque multiproceso falla
Una aplicación multiproceso falla mucho si necesita asignar mucha RAM por hilo. Primero, el uso de RAM en sí mismo significa que no puede manejar tantas solicitudes como una aplicación de subproceso único. Peor aún, malloc es lento. La asignación de montones y montones de objetos (que es común para los marcos web modernos) significa que podemos llegar a ser más lentos que las aplicaciones de subprocesos únicos. Aquí es donde node.js generalmente gana.
Un caso de uso que termina empeorando la multiproceso es cuando necesita ejecutar otro lenguaje de script en su hilo. Primero, generalmente necesita malloc todo el tiempo de ejecución para ese idioma, luego necesita malloc las variables utilizadas por su script.
Entonces, si está escribiendo aplicaciones de red en C o go o java, la sobrecarga de subprocesos generalmente no será tan mala. Si está escribiendo un servidor web C para servir PHP o Ruby, entonces es muy fácil escribir un servidor más rápido en javascript o Ruby o Python.
Enfoque híbrido
Algunos servidores web utilizan un enfoque híbrido. Nginx y Apache2, por ejemplo, implementan su código de procesamiento de red como un grupo de subprocesos de bucles de eventos. Cada subproceso ejecuta un bucle de eventos procesando simultáneamente solicitudes de un solo subproceso, pero las solicitudes tienen equilibrio de carga entre múltiples subprocesos.
Algunas arquitecturas de subproceso único también utilizan un enfoque híbrido. En lugar de iniciar múltiples subprocesos desde un solo proceso, puede iniciar múltiples aplicaciones, por ejemplo, 4 servidores node.js en una máquina de cuatro núcleos. Luego, utiliza un equilibrador de carga para distribuir la carga de trabajo entre los procesos.
En efecto, los dos enfoques son imágenes especulares técnicamente idénticas entre sí.
fuente
Lo que parece estar pensando es que la mayor parte del procesamiento se maneja en el bucle de eventos del nodo. El nodo en realidad explota el trabajo de E / S en subprocesos. Las operaciones de E / S generalmente toman órdenes de magnitud más largas que las operaciones de la CPU, entonces, ¿por qué la CPU espera eso? Además, el sistema operativo ya puede manejar las tareas de E / S muy bien. De hecho, debido a que Node no espera, logra una utilización de CPU mucho mayor.
Por analogía, piense en NodeJS como un camarero que toma los pedidos de los clientes mientras los chefs de E / S los preparan en la cocina. Otros sistemas tienen varios chefs, que toman un pedido de los clientes, preparan la comida, limpian la mesa y solo luego atienden al siguiente cliente.
fuente
Podría estar malinterpretando lo que has dicho aquí, pero "uno a la vez" parece que no estás entendiendo completamente la arquitectura basada en eventos.
En una arquitectura de aplicación "convencional" (no controlada por eventos), el proceso pasa mucho tiempo sentado esperando que algo suceda. En una arquitectura basada en eventos como Node.js, el proceso no solo espera, sino que puede continuar con otro trabajo.
Por ejemplo: obtiene una conexión de un cliente, la acepta, lee los encabezados de la solicitud (en el caso de http), luego comienza a actuar sobre la solicitud. Puede leer el cuerpo de la solicitud, generalmente terminará enviando algunos datos al cliente (esto es una simplificación deliberada del procedimiento, solo para demostrar el punto).
En cada una de estas etapas, la mayor parte del tiempo se pasa esperando que lleguen algunos datos desde el otro extremo; el tiempo real dedicado al procesamiento en el subproceso JS principal suele ser bastante mínimo.
Cuando el estado de un objeto de E / S (como una conexión de red) cambia de manera que necesita procesamiento (por ejemplo, los datos se reciben en un socket, un socket se puede escribir, etc.), el hilo principal Node.js JS se despierta con una lista de artículos que necesitan ser procesados.
Encuentra la estructura de datos relevante y emite algún evento en esa estructura que hace que se ejecuten devoluciones de llamada, que procesan los datos entrantes o escriben más datos en un socket, etc. Una vez que se han procesado todos los objetos de E / S que necesitan procesamiento. procesado, el hilo principal Node.js JS esperará nuevamente hasta que se le indique que hay más datos disponibles (o alguna otra operación se ha completado o ha excedido el tiempo de espera).
La próxima vez que se despierte, bien podría deberse a la necesidad de procesar un objeto de E / S diferente, por ejemplo, una conexión de red diferente. Cada vez, se ejecutan las devoluciones de llamada relevantes y luego vuelve a dormir esperando que ocurra algo más.
El punto importante es que el procesamiento de diferentes solicitudes está intercalado, no procesa una solicitud de principio a fin y luego pasa a la siguiente.
En mi opinión, la principal ventaja de esto es que una solicitud lenta (por ejemplo, si está tratando de enviar 1 MB de datos de respuesta a un dispositivo de teléfono móvil a través de una conexión de datos 2G, o está haciendo una consulta de base de datos realmente lenta) " Bloquee los más rápidos.
En un servidor web multiproceso convencional, normalmente tendrá un subproceso para cada solicitud que se maneja, y procesará SOLO esa solicitud hasta que finalice. ¿Qué sucede si tienes muchas solicitudes lentas? Termina con muchos de sus hilos dando vueltas procesando estas solicitudes, y otras solicitudes (que podrían ser solicitudes muy simples que podrían manejarse muy rápidamente) se ponen en cola detrás de ellas.
Hay muchos otros sistemas basados en eventos además de Node.js, y tienden a tener ventajas y desventajas similares en comparación con el modelo convencional.
No diría que los sistemas basados en eventos son más rápidos en cada situación o con cada carga de trabajo: tienden a funcionar bien para las cargas de trabajo vinculadas a E / S, no tan bien para las vinculadas a CPU.
fuente
Pasos de procesamiento del modelo de bucle de evento de subproceso único
Clientes Enviar solicitud al servidor web.
El servidor web Node JS mantiene internamente un grupo de subprocesos limitados para proporcionar servicios a las solicitudes del cliente.
El servidor web Node JS recibe esas solicitudes y las coloca en una cola. Se conoce como "Cola de eventos".
El servidor web del nodo JS tiene internamente un componente, conocido como "bucle de eventos". Por qué obtuvo este nombre es que usa un bucle indefinido para recibir solicitudes y procesarlas.
Event Loop usa solo un subproceso. Es el corazón principal del modelo de procesamiento de plataforma Node JS.
Event Loop verifica que cualquier solicitud de cliente se coloque en Event Queue. Si no es así, espere las solicitudes entrantes indefinidamente.
En caso afirmativo, recoja una solicitud de cliente de la cola de eventos
Ese subproceso es responsable de tomar esa solicitud, procesarla, realizar operaciones de bloqueo de E / S, preparar la respuesta y enviarla nuevamente al bucle de eventos
muy bien explicado por @Rambabu Posa para obtener más explicaciones, ve a lanzar este enlace
fuente
Agregando a la respuesta de slebetman: cuando dice que
Node.JS
puede manejar 10,000 solicitudes concurrentes, son esencialmente solicitudes sin bloqueo, es decir, estas solicitudes pertenecen principalmente a la consulta de la base de datos.Internamente,
event loop
ofNode.JS
está manejando unthread pool
, donde cada hilo maneja unnon-blocking request
y el bucle de eventos continúa escuchando más solicitudes después de delegar el trabajo a uno de los hilos delthread pool
. Cuando uno de los hilos completa el trabajo, envía una señal alevent loop
que ha terminado akacallback
.Event loop
luego procese esta devolución de llamada y envíe la respuesta de regreso.Como usted es nuevo en NodeJS, lea más sobre
nextTick
cómo entender cómo funciona el bucle de eventos internamente. Lea los blogs en http://javascriptissexy.com , fueron realmente útiles para mí cuando comencé con JavaScript / NodeJS.fuente
Añadiendo a la respuesta de slebetman para más claridad sobre lo que sucede mientras se ejecuta el código.
El grupo de subprocesos internos en nodeJs solo tiene 4 subprocesos de forma predeterminada. y no es como si toda la solicitud estuviera conectada a un nuevo subproceso del grupo de subprocesos, toda la ejecución de la solicitud ocurre como cualquier solicitud normal (sin ninguna tarea de bloqueo), solo que cada vez que una solicitud tiene una ejecución prolongada o una operación pesada como db llamada, una operación de archivo o una solicitud http, la tarea se pone en cola en el grupo de subprocesos interno que proporciona libuv. Y como nodeJs proporciona 4 subprocesos en el grupo de subprocesos interno de forma predeterminada, cada quinta o siguiente solicitud concurrente espera hasta que un subproceso esté libre y una vez que estas operaciones hayan terminado, la devolución de llamada se empuja a la cola de devolución de llamada. y es recogido por el bucle de eventos y devuelve la respuesta.
Ahora aquí viene otra información que no es una sola cola de devolución de llamada, hay muchas colas.
Cada vez que llega una solicitud, el código se ejecuta en este orden de devoluciones de llamada en cola.
No es como cuando hay una solicitud de bloqueo que se adjunta a un nuevo hilo. Solo hay 4 hilos por defecto. Entonces, hay otra fila haciendo cola allí.
Siempre que en un código se produce un proceso de bloqueo como la lectura del archivo, llama a una función que utiliza el subproceso del grupo de subprocesos y luego, una vez que se realiza la operación, la devolución de llamada se pasa a la cola respectiva y luego se ejecuta en el orden.
Todo se pone en cola en función del tipo de devolución de llamada y se procesa en el orden mencionado anteriormente.
fuente