¿Cómo crear servidores de balanceo de carga usando Node.js?

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:

Publicación traducida automáticamente

Artículo escrito por braktim99 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

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