paint-brush
Cómo administrar aplicaciones JS de nodo multiproceso para un mejor rendimientopor@johnjardin
8,421 lecturas
8,421 lecturas

Cómo administrar aplicaciones JS de nodo multiproceso para un mejor rendimiento

por John Jardin6m2021/04/06
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Cómo administrar aplicaciones JS de nodo multiproceso para un mejor rendimiento: cómo potencialmente triplicar el rendimiento de su aplicación de nodo mediante la administración de múltiples subprocesos. Este es un tutorial importante, donde los métodos y los ejemplos que se muestran le brindarán lo que necesita para configurar la administración de subprocesos lista para producción. Los procesos secundarios, la agrupación en clúster y los subprocesos de trabajo solo funcionan para la lógica de JavaScript síncrona que realiza operaciones pesadas, como bucles, cálculos, etc. Si intenta descargar tareas de E/S al grupo de trabajadores, no verá una mejora en el rendimiento.

Company Mentioned

Mention Thumbnail
featured image - Cómo administrar aplicaciones JS de nodo multiproceso para un mejor rendimiento
John Jardin HackerNoon profile picture

En esta publicación, le mostraré cómo triplicar potencialmente el rendimiento de su aplicación Node mediante la administración de múltiples subprocesos. Este es un tutorial importante, donde los métodos y los ejemplos que se muestran le brindarán lo que necesita para configurar la administración de subprocesos lista para producción.

Ver el video en YouTube

Procesos secundarios, agrupación en clústeres y subprocesos de trabajo

Durante mucho tiempo, los Nodos tenían la capacidad de ser multiproceso, mediante el uso de procesos secundarios, agrupación en clústeres o el método preferido más reciente de un módulo llamado subprocesos de trabajo.

Los procesos secundarios fueron el medio inicial para crear múltiples subprocesos para su aplicación y han estado disponibles desde la versión 0.10. Esto se logró generando un proceso de nodo para cada subproceso adicional que deseaba crear.

La agrupación en clústeres, que ha sido una versión estable desde alrededor de la versión 4, nos permite simplificar la creación y administración de procesos secundarios. Funciona brillantemente cuando se combina con PM2 .

Ahora, antes de entrar en subprocesos múltiples de nuestra aplicación, hay algunos puntos que debe comprender completamente:

1. Ya existe subprocesamiento múltiple para tareas de E/S

Hay una capa de Node que ya tiene subprocesos múltiples y es el grupo de subprocesos de libuv . Las tareas de E/S, como la administración de archivos y carpetas, las transacciones TCP/UDP, la compresión y el cifrado se transfieren a libuv y, si no son asíncronas por naturaleza, se manejan en el grupo de subprocesos de libuv.

2. Los procesos secundarios/subprocesos de trabajo solo funcionan para la lógica de JavaScript síncrona

La implementación de subprocesos múltiples mediante procesos secundarios o subprocesos de trabajo solo será efectiva para su código JavaScript síncrono que realiza operaciones pesadas, como bucles, cálculos, etc. Si intenta descargar tareas de E/S a subprocesos de trabajo como ejemplo, obtendrá no ve una mejora en el rendimiento.

3. Crear un hilo es fácil. Administrar múltiples subprocesos dinámicamente es difícil

Crear un hilo adicional en su aplicación es bastante fácil, ya que hay toneladas de tutoriales sobre cómo hacerlo. Sin embargo, crear subprocesos equivalentes a la cantidad de núcleos lógicos que ejecuta su máquina o VM y administrar la distribución del trabajo a estos subprocesos es mucho más avanzado, y para codificar esto, la lógica está por encima de la mayoría de nuestras calificaciones salariales 😎.

Gracias a Dios, estamos en un mundo de código abierto y contribuciones brillantes de la comunidad de Node. Es decir, ya existe un módulo que nos brindará la capacidad total de crear y administrar subprocesos dinámicamente en función de la disponibilidad de la CPU de nuestra máquina o VM.

Grupo de trabajadores

El módulo con el que trabajaremos hoy se llama Worker Pool . Creado por Jos de Jong , Worker Pool ofrece una manera fácil de crear un grupo de trabajadores tanto para la descarga dinámica de cálculos como para la gestión de un grupo de trabajadores dedicados. Es básicamente un administrador de grupos de subprocesos para Node JS, que admite subprocesos de trabajo, procesos secundarios y trabajadores web para implementaciones basadas en navegador.

Para hacer uso del módulo Worker Pool en nuestra aplicación, será necesario realizar las siguientes tareas:

Instalar grupo de trabajadores

Primero, necesitamos instalar el módulo Worker Pool - npm install workerpool

Grupo de trabajadores de inicio

A continuación, necesitaremos inicializar el grupo de trabajadores en el lanzamiento de nuestra aplicación

Crear capa de software intermedio

Luego necesitaremos crear una capa de software intermedio entre nuestra lógica de JavaScript de servicio pesado y el grupo de trabajadores que lo administrará.

Actualizar lógica existente

Finalmente, necesitamos actualizar nuestra aplicación para transferir tareas pesadas al grupo de trabajadores cuando sea necesario.

Gestión de varios subprocesos mediante el grupo de trabajadores

En este punto, tiene 2 opciones: use su propia aplicación NodeJS (e instale los módulos de workerpool y bcryptjs ) o descargue el código fuente de GitHub para este tutorial y mi serie de videos de optimización del rendimiento de NodeJS .

Si opta por lo último, los archivos de este tutorial existirán dentro de la carpeta 06-multithreading . Una vez descargado, ingrese a la carpeta raíz del proyecto y ejecute npm install. Después de eso, ingrese a la carpeta 06-multithreading para seguir.

En la carpeta del grupo de trabajadores , tenemos 2 archivos: uno es la lógica del controlador para el grupo de trabajadores (controller.js). El otro contiene las funciones que activarán los subprocesos... también conocida como la capa de middleware que mencioné anteriormente (thread-functions.js).

grupo de trabajadores/controlador.js

 'use strict' const WorkerPool = require ( 'workerpool' ) const Path = require ( 'path' ) let poolProxy = null // FUNCTIONS const init = async (options) => { const pool = WorkerPool.pool(Path.join(__dirname, './thread-functions.js' ), options) poolProxy = await pool.proxy() console .log( `Worker Threads Enabled - Min Workers: ${pool.minWorkers} - Max Workers: ${pool.maxWorkers} - Worker Type: ${pool.workerType} ` ) } const get = () => { return poolProxy } // EXPORTS exports.init = init exports.get = get

El controlador.js es donde requerimos el módulo de grupo de trabajadores . También tenemos 2 funciones que exportamos, llamadas init y get . La función init se ejecutará una vez durante la carga de nuestra aplicación. Crea una instancia del Worker Pool con opciones que proporcionaremos y una referencia a thread-functions.js . También crea un proxy que se mantendrá en la memoria mientras nuestra aplicación se esté ejecutando. La función get simplemente devuelve el proxy en memoria.

grupo de trabajadores/thread-functions.js

 'use strict' const WorkerPool = require ( 'workerpool' ) const Utilities = require ( '../2-utilities' ) // MIDDLEWARE FUNCTIONS const bcryptHash = ( password ) => { return Utilities.bcryptHash(password) } // CREATE WORKERS WorkerPool.worker({ bcryptHash })

En el archivo thread-functions.js , creamos funciones de trabajo que serán administradas por Worker Pool. Para nuestro ejemplo, usaremos BcryptJS para codificar contraseñas. Esto generalmente toma alrededor de 10 milisegundos para ejecutarse, dependiendo de la velocidad de la máquina, y es un buen caso de uso cuando se trata de tareas pesadas. Dentro del archivo utilities.js está la función y la lógica que codifica la contraseña. Todo lo que estamos haciendo en las funciones de subprocesos es ejecutar este bcryptHash a través de la función de grupo de trabajo. Esto nos permite mantener el código centralizado y evitar la duplicación o confusión de dónde existen ciertas operaciones.

2-utilidades.js

 'use strict' const BCrypt = require ( 'bcryptjs' ) const bcryptHash = async (password) => { return await BCrypt.hash(password, 8 ) } exports.bcryptHash = bcryptHash

.env

 NODE_ENV= "production" PORT= 6000 WORKER_POOL_ENABLED= "1"

El archivo .env contiene el número de puerto y establece la variable NODE_ENV en "producción". También es donde especificamos si queremos habilitar o deshabilitar el grupo de trabajadores, configurando WORKER_POOL_ENABLED en "1" o "0".

1-aplicación.js

 'use strict' require ( 'dotenv' ).config() const Express = require ( 'express' ) const App = Express() const HTTP = require ( 'http' ) const Utilities = require ( './2-utilities' ) const WorkerCon = require ( './worker-pool/controller' ) // Router Setup App.get( '/bcrypt' , async (req, res) => { const password = 'This is a long password' let result = null let workerPool = null if (process.env.WORKER_POOL_ENABLED === '1' ) { workerPool = WorkerCon.get() result = await workerPool.bcryptHash(password) } else { result = await Utilities.bcryptHash(password) } res.send(result) }) // Server Setup const port = process.env.PORT const server = HTTP.createServer(App) ; ( async ( ) => { // Init Worker Pool if (process.env.WORKER_POOL_ENABLED === '1' ) { const options = { minWorkers : 'max' } await WorkerCon.init(options) } // Start Server server.listen(port, () => { console .log( 'NodeJS Performance Optimizations listening on: ' , port) }) })()

Finalmente, nuestro 1-app.js contiene el código que se ejecutará al iniciar nuestra aplicación. Primero, inicializamos las variables en el archivo .env . Luego configuramos un servidor Express y creamos una ruta llamada /bcrypt . Cuando se active esta ruta, verificaremos si el grupo de trabajadores está habilitado. En caso afirmativo, manejamos el proxy Worker Pool y ejecutamos la función bcryptHash que declaramos en el archivo thread-functions.js . Esto a su vez ejecutará la función bcryptHash en Utilidades y nos devolverá el resultado. Si el Worker Pool está deshabilitado, simplemente ejecutamos la función bcryptHash directamente en Utilities .

En la parte inferior de nuestro 1-app.js , verá que tenemos una función de autollamada. Estamos haciendo esto para admitir async/await, que estamos usando cuando interactuamos con Worker Pool. Aquí es donde inicializamos el grupo de trabajadores si está habilitado. La única configuración que queremos anular es establecer minWorkers en "max". Esto asegurará que Worker Pool generará tantos subprocesos como núcleos lógicos haya en nuestra máquina, con la excepción de 1 núcleo lógico, que se utiliza para nuestro subproceso principal. En mi caso, tengo 6 núcleos físicos con hyperthreading, es decir, tengo 12 núcleos lógicos. Entonces, con minWorkers configurado en "max", Worker Pool creará y administrará 11 subprocesos. Finalmente, la última pieza de código es donde iniciamos nuestro servidor y escuchamos en el puerto 6000.

Prueba del grupo de trabajadores

Probar el grupo de trabajadores es tan simple como iniciar la aplicación y, mientras se ejecuta, realizar una solicitud de obtención para

 http://localhost:6000/bcrypt
. Si tiene una herramienta de prueba de carga como AutoCannon , puede divertirse viendo la diferencia en el rendimiento cuando el grupo de trabajadores está habilitado/deshabilitado. AutoCannon es muy fácil de usar.

Conclusión

Espero que este tutorial haya brindado información sobre cómo administrar múltiples subprocesos en su aplicación Node. El video incrustado en la parte superior de este artículo proporciona una demostración en vivo de la prueba de la aplicación Node.

Hasta la próxima, saludos :)

Publicado anteriormente en http://bleedingcode.com/managing-multiple-threads-nodejs/