Si su sitio web o aplicación no recibe muchas requests, no tiene que usar el equilibrio de carga, pero cuando se vuelve muy popular y comienza a recibir una gran cantidad de tráfico, es posible que su servidor subyacente no pueda manejarlo. Porque un solo servidor NodeJS no es flexible para manejar una gran cantidad de tráfico.
Agregar más máquinas puede resolver este problema. Pero para compartir el tráfico con su servidor de aplicaciones, se requiere un balanceador de carga.
Equilibrador de carga: un equilibrador de carga actúa como el policía de tráfico sentado frente a sus servidores de aplicaciones y enrutando las requests de los clientes a través de todos los servidores capaces de cumplir con esas requests de una manera que maximiza la velocidad y la utilización de la capacidad y asegura que ningún servidor esté sobrecargado de trabajo, lo que podría degradar el rendimiento.
¿Cómo configurar el servidor de equilibrio de carga?
1. Uso del Módulo de clúster: NodeJS tiene un módulo integrado llamado Módulo de clúster para aprovechar las ventajas de un sistema multinúcleo. Con este módulo, puede lanzar instancias de NodeJS en cada núcleo de su sistema. Domine el proceso de escucha en un puerto para aceptar las requests de los clientes y distribuirlas entre el trabajador de forma inteligente. Entonces, al usar este módulo, puede utilizar la capacidad de trabajo de su sistema.
El siguiente ejemplo cubre la diferencia de rendimiento usando y sin usar el módulo de clúster.
Sin módulo de clúster:
Asegúrese de haber instalado el módulo express y crypto usando el siguiente comando:
npm install express crypto
índice.js
Javascript
const { generateKeyPair } = require('crypto'); const app = require('express')(); // API endpoint // Send public key as a response app.get('/key', (req, res) => { generateKeyPair('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'top secret' } }, (err, publicKey, privateKey) => { // Handle errors and use the // generated key pair. res.send(publicKey); }) }) app.listen(3000, err => { err ? console.log("Error in server setup") : console.log('Server listening on PORT 3000') });
Ejecute el archivo index.js con el siguiente comando:
node index.js
Salida: Veremos la siguiente salida en la pantalla del terminal:
Server listening on PORT 3000
Ahora abra su navegador y vaya a http://localhost:3000/key , verá el siguiente resultado:
—–BEGIN PUBLIC KEY—– MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwAneYp5HlT93Y3ZlPAHjZAnPFvBskQKKfo4an8jskcgEuG85KnZ7/16kQw2Q8/7Ksdm0sIF7qmAUOu0B773X 1BXQ0liWh+ctHIq/C0e9eM1zOsX6vWwX5Y+WH610cpcb50ltmCeyRmD5Qvf+OE/C BqYrQxVRf4q9+029woF84Lk4tK6OXsdU+Gdqo2FSUzqhwwvYZJJXhW6Gt259m0wD YTZlactvfwhe2EHkHAdN8RdLqiJH9kZV47D6sLS9YG6Ai/HneBIjzTtdXQjqi5vF Y+H+ixZGeShypVHVS119Mi+hnHs7SMzY0GmRleOpna58O1RKPGQg49E7Hr0dz8eh 6QIDAQAB —–END PUBLIC KEY—–
El código anterior escucha en el puerto 3000 y envía la clave pública como respuesta. Generar una clave RSA es un trabajo intensivo de CPU. Aquí solo una instancia de NodeJS trabajando en un solo núcleo. Para ver el rendimiento, hemos utilizado herramientas de cañón automático para probar nuestro servidor como se muestra a continuación:
La imagen de arriba mostró que el servidor puede responder a 2000 requests cuando ejecuta 500 conexiones simultáneas durante 10 segundos. La solicitud promedio por segundo es de 190,1 segundos.
Usando el Módulo de Clúster:
Javascript
const express = require('express'); const cluster = require('cluster'); const { generateKeyPair } = require('crypto'); // Check the number of available CPU. const numCPUs = require('os').cpus().length; const app = express(); const PORT = 3000; // For Master process if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); // Fork workers. for (let i = 0; i < numCPUs; i++) { cluster.fork(); } // This event is firs when worker died cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`); }); } // For Worker else { // Workers can share any TCP connection // In this case it is an HTTP server app.listen(PORT, err => { err ? console.log("Error in server setup") : console.log(`Worker ${process.pid} started`); }); // API endpoint // Send public key app.get('/key', (req, res) => { generateKeyPair('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'top secret' } }, (err, publicKey, privateKey) => { // Handle errors and use the // generated key pair. res.send(publicKey); }) }) }
Ejecute el archivo index.js con el siguiente comando:
node index.js
Salida: Veremos la siguiente salida en la pantalla del terminal:
Master 16916 is running Worker 6504 started Worker 14824 started Worker 20868 started Worker 12312 started Worker 9968 started Worker 16544 started Worker 8676 started Worker 11064 started
Ahora abra su navegador y vaya a http://localhost:3000/key , verá el siguiente resultado:
—–BEGIN PUBLIC KEY—– MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxMQp9y9MblP9dXWuQhf sdlEVnrgmCIyP7CAveYEkI6ua5PJFLRStKHTe3O8rxu+h6I2exXn92F/4RE9Yo8EOnrUCSlqy9bl9qY8D7uBMWir0I65xMZu3rM9Yxi+6gP8H4CMDiJhLoIEap+d9Czr OastDPwI+HF+6nmLkHvuq9X5aORvdiOBwMooIoiRpHbgcHovSerJIfQipGs74IiR 107GbpznSUxMIuwV1fgc6mAULuGZl+Daj0SDxfAjk8KiHyXbfHe5stkPNOCWIsbAtCbGN0bCTR8ZJCLdZ4/VGr+eE0NOvOrElXdXLTDVVzO5dKadoEAtzZzzuQId2P/z JwIDAQAB —–END PUBLIC KEY—–
La aplicación NodeJS anterior se inicia en cada núcleo de nuestro sistema. Donde el proceso maestro acepta la solicitud y la distribuye entre todos los trabajadores. Lo realizado en este caso se muestra a continuación:
La imagen de arriba mostró que el servidor puede responder a 5000 requests cuando ejecuta 500 conexiones simultáneas durante 10 segundos. La solicitud promedio por segundo es de 162,06 segundos.
Entonces, al usar el módulo de clúster, puede manejar más requests. Pero, a veces no es suficiente, si es tu caso entonces tu opción es el escalado horizontal.
2. Uso de Nginx: si su sistema tiene más de un servidor de aplicaciones para responder y necesita distribuir las requests de los clientes en todos los servidores, entonces puede usar Nginx de manera inteligente como un servidor proxy. Nginx se encuentra al frente de su grupo de servidores y distribuye las requests de manera inteligente.
En el siguiente ejemplo, tenemos 4 instancias de la misma aplicación NodeJS en diferentes puertos, también puede usar otro servidor.
El nombre del archivo es index.js
Javascript
const app = require('express')(); // API endpoint app.get('/', (req,res)=>{ res.send("Welcome to GeeksforGeeks !"); }) // Launching application on several ports app.listen(3000); app.listen(3001); app.listen(3002); app.listen(3003);
Ahora instale Nginx en su máquina y cree un nuevo archivo en /etc/nginx/conf.d/ llamado your-domain.com.conf con el siguiente código.
upstream my_http_servers { # httpServer1 listens to port 3000 server 127.0.0.1:3000; # httpServer2 listens to port 3001 server 127.0.0.1:3001; # httpServer3 listens to port 3002 server 127.0.0.1:3002; # httpServer4 listens to port 3003 server 127.0.0.1:3003; } server { listen 80; server_name your-domain.com www.your-domain.com; location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $http_host; proxy_pass http://my_http_servers; } }
3. Uso del servidor web Express: hay muchas ventajas en un servidor web Express. Si se siente cómodo con NodeJS, puede implementar su propio balanceador de carga base Express como se muestra en el siguiente ejemplo.
Paso 1: Cree una aplicación NodeJS vacía.
mkdir LoadBalancer cd LoadBalancer npm init -y
Paso 2: Instale las dependencias requeridas como ExpressJS, axios y simultáneamente usando el siguiente comando.
npm i express axios npm i concurrently -g
Paso 3: Cree dos archivos config.js para el servidor del equilibrador de carga e index.js para el servidor de aplicaciones.
Aquí el nombre del archivo es config.js
Javascript
const express = require('express'); const path = require('path'); const app = express(); const axios = require('axios'); // Application servers const servers = [ "http://localhost:3000", "http://localhost:3001" ] // Track the current application server to send request let current = 0; // Receive new request // Forward to application server const handler = async (req, res) =>{ // Destructure following properties from request object const { method, url, headers, body } = req; // Select the current server to forward the request const server = servers[current]; // Update track to select next server current === (servers.length-1)? current = 0 : current++ try{ // Requesting to underlying application server const response = await axios({ url: `${server}${url}`, method: method, headers: headers, data: body }); // Send back the response data // from application server to client res.send(response.data) } catch(err){ // Send back the error message res.status(500).send("Server error!") } } // Serve favicon.ico image app.get('/favicon.ico', (req, res ) => res.sendFile('/favicon.ico')); // When receive new request // Pass it to handler method app.use((req,res)=>{handler(req, res)}); // Listen on PORT 8080 app.listen(8080, err =>{ err ? console.log("Failed to listen on PORT 8080"): console.log("Load Balancer Server " + "listening on PORT 8080"); });
Aquí, el nombre del archivo es index.js
Javascript
const express = require('express'); const app1 = express(); const app2 = express(); // Handler method const handler = num => (req,res)=>{ const { method, url, headers, body } = req; res.send('Response from server ' + num); } // Only handle GET and POST requests // Receive request and pass to handler method app1.get('*', handler(1)).post('*', handler(1)); app2.get('*', handler(2)).post('*', handler(2)); // Start server on PORT 3000 app1.listen(3000, err =>{ err ? console.log("Failed to listen on PORT 3000"): console.log("Application Server listening on PORT 3000"); }); // Start server on PORT 3001 app2.listen(3001, err =>{ err ? console.log("Failed to listen on PORT 3001"): console.log("Application Server listening on PORT 3001"); });
Explicación: el código anterior comienza con 2 aplicaciones Express, una en el puerto 3000 y otra en el puerto 3001. El proceso del balanceador de carga por separado debe alternar entre estos dos, enviando una solicitud al puerto 3000, la siguiente solicitud al puerto 3001 y la siguiente volver al puerto 3000.
Paso 4: abra un símbolo del sistema en la carpeta de su proyecto y ejecute dos scripts en paralelo al mismo tiempo.
concurrently "node config.js" "node index.js"
Producción:
Veremos el siguiente resultado en la consola:
Ahora, abra un navegador y vaya a http://localhost:8080/ y realice algunas requests, veremos el siguiente resultado: