Application multi-instance Node.js dans PaaS avec Redis Pub / Sub

Si vous avez choisi PaaS comme hébergement pour votre application, vous avez probablement eu ou aurez ce problème: Votre application est déployée dans de petits "conteneurs" (appelés "conteneurs"). dynos à Heroku, ou engrenages dans OpenShift) et vous souhaitez le redimensionner. 

Pour ce faire, vous augmentez le nombre de conteneurs et chaque instance de votre application s'exécute à peu près sur une autre machine virtuelle. Cela est bon pour un certain nombre de raisons, mais cela signifie également que les instances ne partagent pas de mémoire.. 

Dans ce tutoriel, je vais vous montrer comment surmonter ce petit inconvénient..

Lorsque vous avez choisi l'hébergement PaaS, je suppose que vous aviez à l'esprit la mise à l'échelle. Peut-être que votre site a déjà été témoin de l'effet Slashdot ou que vous souhaitez vous y préparer. De toute façon, faire en sorte que les instances communiquent les unes avec les autres est assez simple.

N'oubliez pas que dans l'article, je suppose que vous avez déjà une application Node.js écrite et en cours d'exécution..


Étape 1: Configuration de Redis

Tout d'abord, vous devez préparer votre base de données Redis. J'aime utiliser Redis To Go, car la configuration est très rapide et si vous utilisez Heroku, il y a un add-on (bien que votre compte doive avoir une carte de crédit attribuée). Il existe également Redis Cloud, qui inclut davantage de stockage et de sauvegardes.

À partir de là, la configuration de Heroku est assez simple: sélectionnez le module complémentaire sur la page des modules complémentaires Heroku, puis sélectionnez Redis Cloud ou Redis To Go, ou utilisez l’une des commandes suivantes (notez que la première est destinée à Redis To Go). et le second est pour Redis Cloud):

addons $ heroku: ajoutez redistogo addons $ heroku: ajoutez rediscloud

Étape 2: Configuration de node_redis

À ce stade, nous devons ajouter le module de nœud requis à la package.json fichier. Nous allons utiliser le module recommandé node_redis. Ajouter cette ligne à votre package.json fichier, dans la section des dépendances:

"node_redis": "0.11.x"

Si vous le souhaitez, vous pouvez également inclure embauché, une bibliothèque performante écrite en C, qui node_redis utilisera s'il est disponible:

"embauché": "0.1.x"

Selon la manière dont vous avez créé votre base de données Redis et le fournisseur PaaS que vous utilisez, la configuration de la connexion sera légèrement différente. Vous avez besoin hôte, Port, Nom d'utilisateur, et mot de passe pour votre connexion.

Heroku

Heroku stocke tout dans les variables de configuration sous forme d'URL. Vous devez extraire les informations dont vous avez besoin en utilisant Node's url module (config var pour Redis To Go est process.env.REDISTOGO_URL et pour Redis Cloud process.env.REDISCLOUD_URL). Ce code se trouve en haut de votre fichier d'application 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]); 

Autres

Si vous avez créé la base de données manuellement ou utilisez un fournisseur autre que Heroku, vous devez déjà disposer des options de connexion et des informations d'identification. Utilisez-les simplement:

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

Après cela, nous pouvons commencer à travailler sur la communication entre les instances.


Étape 3: Envoi et réception de données

L'exemple le plus simple consiste simplement à envoyer des informations à d'autres instances que vous venez de démarrer. Par exemple, vous pouvez afficher ces informations dans le panneau d'administration..

Avant de faire quoi que ce soit, créez une autre connexion nommée client2. Je vais expliquer pourquoi nous en avons besoin plus tard.

Commençons par envoyer simplement le message que nous avons commencé. C'est fait en utilisant le publier() méthode du client. Il faut deux arguments: le canal auquel on veut envoyer le message et le texte du message:

client.publish ('instances', 'start'); 

C'est tout ce dont vous avez besoin pour envoyer le message. Nous pouvons écouter les messages dans le message gestionnaire d'événement (notez que nous l'appelons sur notre deuxième client):

client2.on ('message', fonction (canal, message) 

Le rappel est passé les mêmes arguments que nous passons à la publier() méthode. Maintenant, affichons ces informations dans la console:

if ((channel == 'instances') et (message == 'start')) console.log ('Nouvelle instance démarrée!'); );

La dernière chose à faire est de vous abonner au canal que nous utiliserons:

client2.subscribe ('instances');

Nous avons utilisé deux clients pour cela parce que lorsque vous appelez souscrire() sur le client, sa connexion est commutée sur le abonné mode. À partir de ce moment, les seules méthodes que vous pouvez appeler sur le serveur Redis sont SOUSCRIRE et SE DÉSABONNER. Donc si nous sommes dans le abonné mode nous pouvons publier() messages.

Si vous le souhaitez, vous pouvez également envoyer un message lorsque l'instance est en cours de fermeture. Vous pouvez également écouter le message. SIGTERM événement et envoyez le message au même canal:

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

Pour gérer ce cas dans le message gestionnaire ajouter ceci sinon si dedans là:

else if ((channel == 'instances') et (message == 'stop')) console.log ('Instance stoppée!');

Donc, ça ressemble à ça après:

client2.on ('message', fonction (canal, message) si ((canal == 'instances') et (message == 'démarrer')) console.log ('Nouvelle instance démarrée!'); sinon si ( (channel == 'instances') et (message == 'stop')) console.log ('Instance stoppée!'););

Notez que si vous testez sous Windows, il ne prend pas en charge la SIGTERM signal.

Pour le tester localement, démarrez votre application plusieurs fois et observez ce qui se passe dans la console. Si vous souhaitez tester le message de fin, n'émettez pas le Ctrl + C commande dans le terminal, utilisez plutôt tuer commander. Notez que ceci n'est pas supporté sous Windows, vous ne pouvez donc pas le vérifier..

Tout d'abord, utilisez le ps commande pour vérifier quel identifiant votre processus a-pipe à grep pour le rendre plus facile:

$ ps -aux | grep your_apps_name 

La deuxième colonne de la sortie est l'ID que vous recherchez. N'oubliez pas qu'il y aura également une ligne pour la commande que vous venez d'exécuter. Maintenant, exécutez le tuer commande en utilisant 15 pour le signal c'est SIGTERM:

$ kill -15 PID

PID est votre identifiant de processus.


Exemples concrets

Maintenant que vous savez utiliser le protocole Redis Pub / Sub, vous pouvez aller au-delà du simple exemple présenté précédemment. Voici quelques cas d'utilisation qui peuvent être utiles.

Sessions Express

Celui-ci est extrêmement utile si vous utilisez Express.js comme infrastructure. Si votre application prend en charge les connexions utilisateur, ou pratiquement tout ce qui utilise des sessions, vous voudrez vous assurer que les sessions utilisateur sont préservées, que l'instance redémarre ou non, que l'utilisateur se déplace vers un emplacement géré par un autre utilisateur ou que l'utilisateur est commuté à un autre cas parce que l'original est descendu.

Quelques points à retenir:

  • Les instances libres de Redis ne suffiront pas: vous avez besoin de plus de mémoire que les 5 Mo / 25 Mo qu'elles fournissent.
  • Vous aurez besoin d'une autre connexion pour cela.

Nous aurons besoin du module connect-redis. La version dépend de la version d'Express que vous utilisez. Celui-ci est pour Express 3.x:

"connect-redis": "1.4.7"

Et ceci pour Express 4.x:

"connect-redis": "2.x"

Maintenant, créez une autre connexion Redis nommée client_sessions. L’utilisation du module dépend à nouveau de la version Express. Pour 3.x vous créez le RedisStore comme ça:

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

Et en 4.x vous devez passer le session express comme paramètre:

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

Après cela, la configuration est la même dans les deux versions:

app.use (session (store: new RedisStore (client: client_sessions), secret_session), secret: 'votre chaîne secrète'));

Comme vous pouvez le constater, nous passons notre client Redis en tant que client propriété de l'objet transmis à RedisStoreLe constructeur de, puis nous passons le magasin à la session constructeur.

Désormais, si vous démarrez votre application, vous vous connectez ou initiez une session et redémarrez l'instance, votre session sera préservée. La même chose se produit lorsque l'instance est commutée pour l'utilisateur.

Échange de données avec WebSockets

Disons que vous avez une instance complètement séparée (dyno travailleur sur Heroku) pour effectuer davantage de travaux qui consomment des ressources, tels que des calculs complexes, le traitement de données dans la base de données ou l’échange de nombreuses données avec un service externe. Vous voudrez que les instances "normales" (et donc les utilisateurs) connaissent le résultat de ce travail une fois terminé.

Selon que vous souhaitez que les instances Web envoient des données à l’utilisateur, vous aurez besoin d’une ou deux connexions (appelons-les client_sub et client_pub sur le travailleur aussi). Vous pouvez également réutiliser toute connexion qui n’abonne rien (comme celle que vous utilisez pour les sessions Express) au lieu de la client_pub.

Désormais, lorsque l'utilisateur souhaite exécuter l'action, vous publiez le message sur le canal réservé à cet utilisateur et à ce travail spécifique:

// cela entre dans votre gestionnaire de requêtes client_pub.publish ('JOB: USERID: JOBNAME: START', JSON.stringify (THEDATAYOUWANTTOSEND)); client_sub.subscribe ('JOB: USERID: JOBNAME: PROGRESS');

Bien sûr, vous devrez remplacer IDENTIFIANT D'UTILISATEUR et NOM DU TRAVAIL avec des valeurs appropriées. Vous devriez aussi avoir le message gestionnaire préparé pour le client_sub lien:

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

Ceci extrait le IDENTIFIANT D'UTILISATEUR à partir du nom du canal (assurez-vous de ne pas vous abonner à des canaux non liés aux travaux des utilisateurs sur cette connexion) et envoyez le message au client approprié. En fonction de la bibliothèque WebSocket que vous utilisez, il sera possible d'accéder à un socket par son ID..

Vous vous demandez peut-être comment l'instance de travail peut s'abonner à tous ces canaux. Bien sûr, vous ne voulez pas juste faire quelques boucles sur tous les possibles IDENTIFIANT D'UTILISATEURle sable NOM DU TRAVAILs. le psubscribe () La méthode accepte un motif comme argument pour pouvoir souscrire à tous EMPLOI:* canaux:

// ce code va à l'instance de travailleur // et vous l'appelez UNE FOIS client_sub.psubscribe ('JOB: *')

Problèmes communs

Il existe quelques problèmes que vous pouvez rencontrer lors de l’utilisation de Pub / Sub:

  • Votre connexion au serveur Redis est refusée. Si cela se produit, assurez-vous de fournir les options de connexion et les informations d'identification appropriées, et que le nombre maximal de connexions n'a pas été atteint..
  • Vos messages ne sont pas livrés. Si cela se produit, vérifiez que vous êtes abonné au même canal que celui sur lequel vous envoyez des messages (cela semble idiot, mais cela arrive parfois). Assurez-vous également que vous attachez le message gestionnaire avant d'appeler souscrire(), et que vous appelez souscrire() sur une instance avant d'appeler publier() de l'autre.