Aplicación Node.js de instancias múltiples en PaaS usando Redis Pub / Sub

Si eligió PaaS como alojamiento para su aplicación, probablemente tuvo o tendrá este problema: su aplicación se implementa en pequeños "contenedores" (conocidos como dynos en Heroku, o engranajes en OpenShift) y quieres escalarlo. 

Para hacerlo, aumenta la cantidad de contenedores, y cada instancia de su aplicación se está ejecutando prácticamente en otra máquina virtual. Esto es bueno por varias razones, pero también significa que las instancias no comparten memoria. 

En este tutorial te mostraré cómo superar este pequeño inconveniente..

Cuando eligió el alojamiento de PaaS, asumo que tenía en mente la escala. Tal vez su sitio ya haya presenciado el efecto Slashdot o si desea prepararse para ello. De cualquier manera, hacer que las instancias se comuniquen entre sí es bastante simple..

Tenga en cuenta que en el artículo asumiré que ya tiene una aplicación Node.js escrita y en ejecución.


Paso 1: Configuración de Redis

Primero, tienes que preparar tu base de datos de Redis. Me gusta usar Redis To Go, porque la configuración es muy rápida, y si está utilizando Heroku hay un complemento (aunque su cuenta debe tener una tarjeta de crédito asignada). También está Redis Cloud, que incluye más almacenamiento y copias de seguridad..

Desde allí, la configuración de Heroku es bastante sencilla: seleccione el complemento en la página de complementos de Heroku y seleccione Redis Cloud o Redis To Go, o use uno de los siguientes comandos (tenga en cuenta que el primero es para Redis To Go , y el segundo es para Redis Cloud):

Complementos de $ heroku: agregue redistogo Complementos de $ heroku: agregue rediscloud

Paso 2: Configuración de node_redis

En este punto, tenemos que agregar el módulo Nodo requerido a la paquete.json expediente. Usaremos el módulo node_redis recomendado. Añade esta línea a tu paquete.json archivo, en la sección de dependencias:

"node_redis": "0.11.x"

Si lo desea, también puede incluir hiredis, una biblioteca de alto rendimiento escrita en C, que node_redis utilizará si está disponible:

"hiredis": "0.1.x"

Dependiendo de cómo haya creado su base de datos Redis y de qué proveedor de PaaS utiliza, la configuración de la conexión se verá un poco diferente. Necesitas anfitrión, Puerto, nombre de usuario, y contraseña para su conexión.

Heroku

Heroku almacena todo en las variables de configuración como URLs. Tienes que extraer la información que necesitas de ellos usando Node's url módulo (config var para Redis To Go es process.env.REDISTOGO_URL y para Redis Cloud process.env.REDISCLOUD_URL). Este código va en la parte superior de su archivo de aplicación principal:

var redis = require ('redis'); var url = require ('url'); var redisURL = url.parse (YOUR_CONFIG_VAR_HERE); var client = redis.createClient (redisURL.host, redisURL.port); client.auth (redisURL.auth.split (':') [1]); 

Otros

Si creó la base de datos a mano, o usa un proveedor que no sea Heroku, ya debería tener las opciones de conexión y las credenciales, así que simplemente úselos:

var redis = require ('redis'); var client = redis.createClient (YOUR_HOST, YOUR_PORT); client.auth (YOUR_PASSWORD);

Después de eso podemos empezar a trabajar en la comunicación entre instancias..


Paso 3: Enviar y recibir datos

El ejemplo más simple simplemente enviará información a otras instancias que acaba de comenzar. Por ejemplo, puede mostrar esta información en el panel de administración.

Antes de hacer nada, crea otra conexión llamada cliente2. Te explicaré por qué lo necesitamos más tarde..

Comencemos simplemente enviando el mensaje que empezamos. Se hace usando el publicar() Método del cliente. Toma dos argumentos: el canal al que queremos enviar el mensaje y el texto del mensaje:

client.publish ('instancias', 'inicio'); 

Eso es todo lo que necesitas para enviar el mensaje. Podemos escuchar mensajes en el mensaje manejador de eventos (note que lo llamamos en nuestro segundo cliente):

client2.on ('mensaje', función (canal, mensaje) 

La devolución de llamada pasa los mismos argumentos que pasamos a la publicar() método. Ahora vamos a mostrar esta información en la consola:

if ((canal == 'instancias') y (mensaje == 'inicio')) console.log ('¡Nueva instancia iniciada!'); );

Lo último que debe hacer es suscribirse al canal que utilizaremos:

client2.subscribe ('instancias');

Usamos dos clientes para esto porque cuando llamas suscribir() En el cliente, su conexión se conmuta a la abonado modo. A partir de ese momento, los únicos métodos que puede llamar en el servidor de Redis son SUSCRIBIR y Suscribirse. Así que si estamos en el abonado modo podemos publicar() mensajes.

Si lo desea, también puede enviar un mensaje cuando la instancia se está cerrando, puede escuchar la Sigma evento y enviar el mensaje al mismo canal:

process.on ('SIGTERM', function () client.publish ('instancias', 'stop'); process.exit ();); 

Para manejar ese caso en el mensaje controlador agregar este si no ahí:

si no ((canal == 'instancias') y (mensaje == 'detener')) console.log ('Instancia detenida!');

Así se ve esto después:

client2.on ('mensaje', función (canal, mensaje) si ((canal == 'instancias') y (mensaje == 'inicio')) console.log ('¡Nueva instancia comenzó!'); de lo contrario, si (canal == 'instancias') y (mensaje == 'parada')) console.log ('Instancia detenida!'););

Tenga en cuenta que si está probando en Windows, no es compatible con Sigma señal.

Para probarlo localmente, inicie su aplicación varias veces y vea qué sucede en la consola. Si desea probar el mensaje de terminación, no emita el Ctrl + C comando en el terminal, en su lugar, use el matar mando. Tenga en cuenta que esto no es compatible con Windows, por lo que no puede verificarlo.

Primero, use el PD comando para comprobar qué ID tiene su proceso para canalizarlo grep Hacerlo más fácil:

$ ps -aux | grep your_apps_name 

La segunda columna de la salida es el ID que está buscando. Tenga en cuenta que también habrá una línea para el comando que acaba de ejecutar. Ahora ejecuta el matar comando usando 15 para la señal es Sigma:

$ kill -15 PID

PID es su ID de proceso.


Ejemplos del mundo real

Ahora que sabe cómo usar el protocolo Redis Pub / Sub, puede ir más allá del simple ejemplo presentado anteriormente. Aquí hay algunos casos de uso que pueden ser útiles..

Sesiones Express

Este es extremadamente útil si está utilizando Express.js como su marco. Si su aplicación admite inicios de sesión de usuario, o casi cualquier cosa que utilice sesiones, querrá asegurarse de que las sesiones de usuario se conserven, no importa si la instancia se reinicia, el usuario se mueve a una ubicación que es manejada por otra o por el usuario. Se cambia a otra instancia porque el original bajó..

Algunas cosas para recordar:

  • Las instancias gratuitas de Redis no serán suficientes: necesita más memoria que los 5MB / 25MB que proporcionan.
  • Necesitará otra conexión para esto..

Necesitaremos el módulo connect-redis. La versión depende de la versión de Express que esté utilizando. Este es para Express 3.x:

"connect-redis": "1.4.7"

Y esto para Express 4.x:

"connect-redis": "2.x"

Ahora crea otra conexión Redis llamada sesiones de cliente. El uso del módulo nuevamente depende de la versión Express. Para 3.x creas la RedisStore Me gusta esto:

var RedisStore = require ('connect-redis') (express)

Y en 4.x tienes que pasar el sesión expresa como el parámetro:

var session = require ('express-session'); var RedisStore = require ('connect-redis') (sesión);

Después de eso la configuración es la misma en ambas versiones:

app.use (session (store: new RedisStore (client: client_sessions), secreto: 'su cadena secreta'));

Como puede ver, estamos pasando a nuestro cliente Redis como el cliente propiedad del objeto pasado a RedisStoredel constructor, y luego pasamos la tienda a la sesión constructor.

Ahora, si inicia su aplicación, inicia sesión o inicia una sesión y reinicia la instancia, se conservará su sesión. Lo mismo sucede cuando se cambia la instancia para el usuario..

Intercambio de datos con WebSockets

Digamos que tienes una instancia completamente separada (dinamo trabajador en Heroku) para realizar más tareas de consumo de recursos, como cálculos complicados, procesar datos en la base de datos o intercambiar muchos datos con un servicio externo. Deseará que las instancias "normales" (y, por lo tanto, los usuarios) conozcan el resultado de este trabajo cuando haya terminado..

Dependiendo de si desea que las instancias web envíen datos al trabajador, necesitará una o dos conexiones (nombrémoslas client_sub y cliente_pub en el trabajador también). También puede reutilizar cualquier conexión que no se suscriba a nada (como la que usa para las sesiones Express) en lugar de la cliente_pub.

Ahora cuando el usuario desea realizar la acción, usted publica el mensaje en el canal que está reservado solo para este usuario y para este trabajo específico:

// esto se incluye en su manejador de solicitudes client_pub.publish ('JOB: USERID: JOBNAME: START', JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ('JOB: USERID: JOBNAME: PROGRESS');

Por supuesto que tendrás que reemplazar Usuario y NOMBRE DEL TRABAJO con los valores adecuados. También deberías tener el mensaje manejador preparado para el client_sub conexión:

client_sub.on ('message', function (channel, message) var USERID = channel.split (':') [1]; if (message == 'DONE') client_sub.unsubscribe (channel); sockets [USERID] .emit (canal, mensaje););

Esto extrae el Usuario desde el nombre del canal (así que asegúrese de no suscribirse a canales que no estén relacionados con trabajos de usuario en esta conexión) y envíe el mensaje al cliente correspondiente. Dependiendo de la biblioteca WebSocket que use, habrá alguna forma de acceder a un socket por su ID.

Puede preguntarse cómo la instancia de trabajo puede suscribirse a todos esos canales. Por supuesto, no solo desea hacer algunos bucles en todos los posibles Usuarios y NOMBRE DEL TRABAJOs. los psubscribe () El método acepta un patrón como argumento, por lo que puede suscribirse a todos TRABAJO:* canales

// este código va a la instancia de trabajo // y lo llama ONCE client_sub.psubscribe ('JOB: *')

Problemas comunes

Hay algunos problemas que puede encontrar al usar Pub / Sub:

  • Su conexión al servidor de Redis es rechazada. Si esto sucede, asegúrese de proporcionar las opciones de conexión y las credenciales adecuadas, y de que no se haya alcanzado el número máximo de conexiones.
  • Tus mensajes no son entregados. Si esto sucede, verifique que se haya suscrito al mismo canal en el que está enviando los mensajes (parece una tontería, pero a veces sucede). Asegúrese también de que adjunta el mensaje manejador antes de llamar suscribir(), y que llamas suscribir() en una instancia antes de llamar publicar() en el otro.