Il y a 8 ans -
Temps de lecture 4 minutes
Clusteriser votre application Node.js
Les application Node.js sont par nature mono-threadées, or les serveurs, de nos jours, sont presque* toujours multi-core. Pour exploiter l’ensemble des capacités de ces serveurs, il est nécessaire de pouvoir exploiter tous les cores.
Pour cela, il existe principalement 2 techniques:
- Lancer plusieurs instances d’une application Node.js sur différents servers avec un reverse proxy pour load balancer les requêtes entrantes
- Lancer une application Node.js en mode cluster
Dans l’idéal, il faut lancer autant d’instances qu’il y a de cores sur la machine. Cela permet de partager au mieux la puissance de la machine entre les différentes instances sans pour autant dégrader les performances en partageant les cores entre plusieurs instances.
Nous allons dans cet article nous intéresser au second cas de figure, c’est à dire le lancement d’application Node.js en mode cluster.
* Le terme “presque” est utilisé ici car de nombreux serveurs cloud d’entrée de gamme restent mono-threadés (VPS et instances EC2 1er prix, …).
Le module cluster
Le module cluster, bien que marqué comme ayant une API expérimentale dans la documentation de Node.js, est aujourd’hui largement utilisé.
Son principe est simple, lorsqu’une application est lancée en mode cluster, un premier process est démarré en mode master. Le process master n’a pas pour rôle de traiter les requêtes entrantes à proprement parler, mais plutôt à les dispatcher aux process forkés qui eux sont dédiés au traitement des requêtes. Ils sont lancés dans le mode worker.
La responsabilité de l’instanciation de forks en mode worker est du ressort du process master, et les règles de fork sont laissées à la responsabilité du développeur. Tout au long de la vie du cluster, des événements sont générés aussi bien par le master que par les workers. Il est important de s’y abonner pour être capable de réagir à des changements dans le cluster (Crash d’un worker, par exemple).
Un exemple simple de cluster est proposé par la documentation :
var http = require("http");
var numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
var i = 0;
while (i < numCPUs) {
cluster.fork();
i++;
}
cluster.on("exit", function(worker, code, signal) {
console.log("worker " + worker.process.pid + " died");
});
} else {
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}[/javascript]
Bien que les API de clusterisation proposent une API assez simple à gérer, il faut néanmoins s’occuper de plusieurs points de détail, et le risque de mal faire est rapidement arrivé. Il est donc conseillé de s’appuyer sur un module tiers pour traiter ce sujet.
Le module recluster, très intéressant de part sa simplicité et sa maturité, permet de s’affranchir de toute cette complexité de mise en oeuvre.
recluster
Pour l’installer, il suffit de taper la commande suivante :
Pour instancer une application en mode cluster, il suffit d’ajouter le script suivant (cluster.js, par exemple) à votre application :
var path = require("path");
var cluster = recluster(path.join(__dirname, "server.js"), {
/* Options */
});
cluster.run();
process.on("SIGUSR2", function() {
console.log("Signal SIGUSR2 reçu, Rechargement du cluster …");
cluster.reload();
});
console.log("Cluster démarré, Utilisez la commande ‘kill -s SIGUSR2 " + process.pid + "’ pour le recharger.");[/javascript]
Le module recluster attend en paramètre le chemin du script. Il suffit ensuite de lancer le fichier cluster.js au lieu du fichier server.js, et le tour est joué !
Zero downtime reloading
Le modules recluster permet de mettre à jour une application sans coupure de service, il faut pour cela appeler la fonction reload sur la variable cluster.
Dans l’exemple ci-dessus, le signal SIGUSR2, permet d’indiquer au programme que le cluster doit être rechargé. Celui-ci rechargera les différents workers en se basant sur les options passées en paramètre (timeout de rechargement, etc). Les requêtes en cours de traitement seront honorées sans coupure brutale, dans la limite d’un timeout défini par les options de configuration du module.
Cette fonctionnalité est particulièrement intéressante lorsque l’application doit être mise à jour sans coupure de service. Ainsi, la base de code peut-être mise à jour, puis les workers redémarrés un par un une fois les requêtes en cours traitées.
Il est possible de s’appuyer sur d’autres conditions pour recharger les workers d’un cluster, par exemple, il est possible de s’appuyer sur la modification du fichier package.json pour déclencher un rechargement avec le code suivant:
fs.watchFile("package.json", function(curr, prev) {
console.log("Package.json changé, rechargement du cluster …");
cluster.reload();
});
[/javascript]
Conclusion
Ne vous laissez pas impressionner par la notion de clusterisation. Elle est très simple à mettre en oeuvre dans le monde Node.js grâce à une API de base faisant parti des core modules, et de nombreux modules s’appuyant dessus.
Commentaire
4 réponses pour " Clusteriser votre application Node.js "
Published by Jérémy , Il y a 8 ans
Bonjour,
Merci, je ne connaissait pas ‘recluster’.
Par contre est-il possible de communiqué entre les processus forké ou avec le ‘maitre’ ?
Quelque chose comme :
var cluster = recluster(path.join(__dirname, "server.js"), {
/* Options */
});
cluster.on("forked_process_message", function() {
console.log("Message reçut du processus forké");
});
cluster.run();
Published by Alexis Kinsella , Il y a 8 ans
Bonjour,
Le module recluster ne fait que réutiliser les API du module cluster de Node. Tu peux donc utiliser les méthodes habituelles pour communiquer entre les différents process du cluster.
Par exemple, tu peux écouter les message en t’abonnant aux events ‘message’ du process:
process.on(‘message’, function(message) {
if (!message.type) {
logger.warn(« [APP] Message type expected: » + JSON.stringify(message) + » – ignoring message »);
}
if (!message.payload) {
logger.warn(« [APP] Message payload expected: » + JSON.stringify(message) + » – ignoring message »);
}
if (message.type === ‘CONTEXT_MESSAGE’) {
return process.emit(‘CONTEXT_MESSAGE’, message.payload);
} else {
return logger.warn(« [APP] Message type not supported: » + message.type + » – ignoring message »);
}
});
Ici, nous attendons que le message reçu par le worker dispose de certains champs obligatoires, avant de le ré-émettre dans le process en cours. ( Mais on peut imaginer faire d’autre choses. )
Tu peux également diffuser des messages par exemple du master vers les workers de ton cluster avec le code suivant:
var workers = this.cluster.workers;
for (var i = 0, var len = workers.length; i < len; i++) { var worker = workers[i]; worker.send({ type: 'CONTEXT_MESSAGE', payload: payload }); }
Published by Owumaro , Il y a 8 ans
Bonjour,
Une alternative intéressante pour lancer une appli node.js en mode cluster : l’outil pm2 qui s’occupe de tout. https://github.com/Unitech/pm2
Je cite : « With the cluster mode, PM2 enables load balancing between each worker. Each HTTP/TCP/UDP request will be forwarded to one specific process at a time. »
Un avis là dessus ? :)
Published by Alexis Kinsella , Il y a 8 ans
Bonjour,
L’outil PM2 est intéressant à plus d’un titre, mais je n’irai pas jusqu’à le conseiller en production. J’ai essayé de le mettre en place lorsque le projet était peut-être un jeune et je m’en suis mordu les doigts. Les problèmes que j’ai rencontré ont été, il me semble, corrigés depuis, mais j’en ai gardé une mauvaise expérience. Depuis, j’ai décidé de m’appuyer sur les outils mise à disposition par Linux pour gérer mes applications en tant que service avec Upstart qui propose des fonctionnalités intéressantes telles que le respawn.
Etant donné le niveau d’activité sur le projet PM2 et les fonctionnalités proposées, ça vaut néanmoins le coup de l’évaluer à niveau pour bénéficier que fonctionnalités qu’une intégration système simple ne peut proposer.