Configurar un cl煤ster de Node.js

    Todos sabemos que Node.js es excelente para manejar muchos eventos de forma asincr贸nica, pero lo que mucha gente no sabe es que todo esto se hace en un solo hilo. Node.js en realidad no es multiproceso, por lo que todas estas solicitudes solo se manejan en el bucle de eventos de un solo hilo.

    Entonces, 驴por qu茅 no aprovechar al m谩ximo su procesador de cuatro n煤cleos utilizando un cl煤ster Node.js? Esto iniciar谩 varias instancias de su c贸digo para manejar a煤n m谩s solicitudes. Esto puede parecer un poco dif铆cil, pero en realidad es bastante f谩cil de hacer con racimo m贸dulo, que se introdujo en Node.js v0.8.

    Obviamente, esto es 煤til para cualquier aplicaci贸n que pueda dividir el trabajo entre diferentes procesos, pero es especialmente importante para las aplicaciones que manejan muchas solicitudes de IO, como un sitio web.

    Desafortunadamente, debido a las complejidades del procesamiento paralelo, agrupar una aplicaci贸n en un servidor no siempre es sencillo. 驴Qu茅 hace cuando necesita varios procesos para escuchar en el mismo puerto? Recuerde que solo un proceso puede acceder a un puerto en un momento dado. La soluci贸n ingenua aqu铆 es configurar cada proceso para escuchar en un puerto diferente y luego configurar Nginx para equilibrio de carga solicitudes entre los puertos.

    Esta es una soluci贸n viable, pero requiere mucho m谩s trabajo para configurar y configurar cada proceso, sin mencionar la configuraci贸n de Nginx. Con esta soluci贸n, solo est谩 agregando m谩s cosas para que usted las administre.

    En su lugar, puede bifurcar el proceso maestro en varios procesos secundarios (por lo general, tiene un hijo por procesador). En este caso, los ni帽os pueden compartir un puerto con el padre (gracias a la comunicaci贸n entre procesos, o IPC), por lo que no es necesario preocuparse por administrar varios puertos.

    Esto es exactamente lo que cluster m贸dulo lo hace por usted.

    Trabajar con el m贸dulo de cl煤ster

    Agrupar una aplicaci贸n es extremadamente simple, especialmente para c贸digo de servidor web como R谩pido proyectos. Todo lo que necesitas hacer es esto:

    var cluster = require('cluster');
    var express = require('express');
    var numCPUs = require('os').cpus().length;
    
    if (cluster.isMaster) {
        for (var i = 0; i < numCPUs; i++) {
            // Create a worker
            cluster.fork();
        }
    } else {
        // Workers share the TCP connection in this server
        var app = express();
    
        app.get("https://Pharos.sh.com/", function (req, res) {
            res.send('Hello World!');
        });
    
        // All workers use this port
        app.listen(8080);
    }
    

    La funcionalidad del c贸digo se divide en dos partes, el c贸digo maestro y el c贸digo del trabajador. Esto se hace en la sentencia if (if (cluster.isMaster) {...}). El 煤nico prop贸sito del maestro aqu铆 es crear todos los trabajadores (la cantidad de trabajadores creados se basa en la cantidad de CPU disponibles), y los trabajadores son responsables de ejecutar instancias separadas del servidor Express.

    Cuando un trabajador se bifurca fuera del proceso principal, vuelve a ejecutar el c贸digo desde el principio del m贸dulo. Cuando el trabajador llega a la sentencia if, devuelve false para cluster.isMaster, entonces, crear谩 la aplicaci贸n Express, una ruta, y luego escuchar谩 en el puerto 8080. En el caso de un procesador de cuatro n煤cleos, tendr铆amos cuatro trabajadores engendrados, todos escuchando en el mismo puerto para recibir solicitudes.

    Pero, 驴c贸mo se reparten las solicitudes entre los trabajadores? Obviamente, no pueden (y no deber铆an) estar todos escuchando y respondiendo a cada solicitud que recibimos. Para manejar esto, en realidad hay un balanceador de carga integrado dentro del cluster m贸dulo que maneja la distribuci贸n de solicitudes entre los diferentes trabajadores. En Linux y OSX (pero no en Windows), el round-robin (cluster.SCHED_RR) est谩 en vigor de forma predeterminada. La 煤nica otra opci贸n de programaci贸n disponible es dejarla en manos del sistema operativo (cluster.SCHED_NONE), que es el predeterminado en Windows.

    La pol铆tica de programaci贸n se puede establecer en cluster.schedulingPolicy o configur谩ndolo en la variable de entorno NODE_CLUSTER_SCHED_POLICY (con valores de ‘rr’ o ‘none’).

    Quiz谩s tambi茅n se pregunte c贸mo diferentes procesos pueden compartir un solo puerto. La parte dif铆cil de ejecutar tantos procesos que manejan solicitudes de red es que tradicionalmente solo uno puede tener un puerto abierto a la vez. El gran beneficio de cluster es que maneja el uso compartido de puertos por usted, por lo que cualquier puerto que tenga abierto, como para un servidor web, ser谩 accesible para todos los ni帽os. Esto se hace a trav茅s de IPC, lo que significa que el maestro simplemente env铆a el identificador del puerto a cada trabajador.

    Gracias a caracter铆sticas como esta, la agrupaci贸n en cl煤steres es muy f谩cil.

    cluster.fork () vs child_process.fork ()

    Si tiene experiencia previa con child_processes fork() m茅todo, entonces puede estar pensando que cluster.fork() es algo similar (y lo son, en muchos sentidos), por lo que explicaremos algunas diferencias clave sobre estos dos m茅todos de bifurcaci贸n en esta secci贸n.

    Hay algunas diferencias principales entre cluster.fork() y child_process.fork(). los child_process.fork() El m茅todo es un poco m谩s bajo y requiere que pase la ubicaci贸n (ruta del archivo) del m贸dulo como argumento, adem谩s de otros argumentos opcionales como el directorio de trabajo actual, el usuario propietario del proceso, las variables de entorno y m谩s.

    Otra diferencia es que cluster inicia la ejecuci贸n del trabajador desde el principio del mismo m贸dulo desde el que se ejecut贸. Entonces, si el punto de entrada de su aplicaci贸n es index.js, pero el trabajador se genera en cluster-my-app.js, entonces todav铆a comenzar谩 su ejecuci贸n desde el principio en index.js. child_process es diferente en que genera la ejecuci贸n en cualquier archivo que se le pase, y no necesariamente en el punto de entrada de la aplicaci贸n dada.

    Es posible que ya haya adivinado que el cluster m贸dulo realmente utiliza el child_process m贸dulo de abajo para crear los ni帽os, que se hace con child_processes propio fork() m茅todo, lo que les permite comunicarse a trav茅s de IPC, que es c贸mo se comparten los identificadores de puerto entre los trabajadores.

    Para ser claros, la bifurcaci贸n en Node es muy diferente a una Horquilla POISIX ya que en realidad no clona el proceso actual, sino que inicia una nueva instancia V8.

    Aunque esta es una de las formas m谩s f谩ciles de realizar m煤ltiples subprocesos, debe usarse con precauci贸n. El hecho de que puedas generar 1.000 trabajadores no significa que debas hacerlo. Cada trabajador consume recursos del sistema, por lo que solo genera los que realmente se necesitan. Los documentos de Node establecen que, dado que cada proceso hijo es una nueva instancia de V8, debe esperar un tiempo de inicio de 30 ms para cada uno y al menos 10 MB de memoria por instancia.

    Manejo de errores

    Entonces, 驴qu茅 hace cuando uno (隆o m谩s!) De sus trabajadores muere? El objetivo de la agrupaci贸n en cl煤steres se pierde b谩sicamente si no puede reiniciar los trabajadores despu茅s de que se bloquean. Por suerte para ti el cluster el m贸dulo se extiende EventEmitter y proporciona un evento de “salida”, que le indica cu谩ndo muere uno de sus hijos trabajadores.

    Puede usar esto para registrar el evento y reiniciar el proceso:

    cluster.on('exit', function(worker, code, signal) {
        console.log('Worker %d died with code/signal %s. Restarting worker...', worker.process.pid, signal || code);
        cluster.fork();
    });
    

    Ahora, despu茅s de solo 4 l铆neas de c贸digo, 隆es como si tuviera su propio administrador de procesos interno!

    Comparaciones de desempe帽o

    Bien, ahora a la parte interesante. Veamos cu谩nto nos ayuda realmente la agrupaci贸n en cl煤steres.

    Para este experimento, configur茅 una aplicaci贸n web similar al c贸digo de ejemplo que mostr茅 arriba. Pero la mayor diferencia es que estamos simulando el trabajo que se realiza dentro de la ruta Express utilizando el dormir m贸dulo y devolviendo un mont贸n de datos aleatorios al usuario.

    Aqu铆 est谩 la misma aplicaci贸n web, pero con agrupamiento:

    var cluster = require('cluster');
    var crypto = require('crypto');
    var express = require('express');
    var sleep = require('sleep');
    var numCPUs = require('os').cpus().length;
    
    if (cluster.isMaster) {
        for (var i = 0; i < numCPUs; i++) {
            // Create a worker
            cluster.fork();
        }
    } else {
        // Workers share the TCP connection in this server
        var app = express();
    
        app.get("https://Pharos.sh.com/", function (req, res) {
            // Simulate route processing delay
            var randSleep = Math.round(10000 + (Math.random() * 10000));
            sleep.usleep(randSleep);
    
            var numChars = Math.round(5000 + (Math.random() * 5000));
            var randChars = crypto.randomBytes(numChars).toString('hex');
            res.send(randChars);
        });
    
        // All workers use this port
        app.listen(8080);
    }
    

    Y aqu铆 est谩 el c贸digo de ‘control’ a partir del cual haremos nuestras comparaciones. Es esencialmente lo mismo, solo que sin cluster.fork():

    var crypto = require('crypto');
    var express = require('express');
    var sleep = require('sleep');
    
    var app = express();
    
    app.get("https://Pharos.sh.com/", function (req, res) {
        // Simulate route processing delay
        var randSleep = Math.round(10000 + (Math.random() * 10000));
        sleep.usleep(randSleep);
    
        var numChars = Math.round(5000 + (Math.random() * 5000));
        var randChars = crypto.randomBytes(numChars).toString('hex');
        res.send(randChars);
    });
    
    app.listen(8080);
    

    Para simular una gran carga de usuarios, usaremos una herramienta de l铆nea de comandos llamada Cerco, que podemos usar para realizar un mont贸n de solicitudes simult谩neas a la URL de nuestra elecci贸n.

    Siege tambi茅n es bueno porque rastrea las m茅tricas de rendimiento, como la disponibilidad, el rendimiento y la tasa de solicitudes manejadas.

    Aqu铆 est谩 el comando Siege que usaremos para las pruebas:

    $ siege -c100 -t60s http://localhost:8080/
    

    Despu茅s de ejecutar este comando para ambas versiones de la aplicaci贸n, estos son algunos de los resultados m谩s interesantes:

    Tipo Total de solicitudes gestionadas Solicitudes / segundo Tiempo medio de respuesta Rendimiento

    Sin agrupamiento346758,691,18 segundos0,84 MB / s
    Agrupaci贸n (4 procesos)11146188,720.03 segundos2,70 MB / s

    Como puede ver, la aplicaci贸n agrupada tiene una mejora de alrededor de 3.2x sobre la aplicaci贸n de proceso 煤nico para casi todas las m茅tricas enumeradas, excepto el tiempo de respuesta promedio, que tiene una mejora mucho m谩s significativa.

     

    Etiquetas:

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *