Scaling Socket.Io à Noeud multiple.processus js utilisant cluster

me déchirant les cheveux avec celui-ci... quelqu'un a réussi à l'échelle Socket.IO à plusieurs "travailleur" processus engendré par Nœud.module cluster DE js?

disons que j'ai le texte suivant sur quatre les processus de travail (pseudo):

// on the server
var express = require('express');
var server = express();
var socket = require('socket.io');
var io = socket.listen(server);

// socket.io
io.set('store', new socket.RedisStore);

// set-up connections...
io.sockets.on('connection', function(socket) {

  socket.on('join', function(rooms) {
    rooms.forEach(function(room) {
      socket.join(room);
    });
  });

  socket.on('leave', function(rooms) {
    rooms.forEach(function(room) {
      socket.leave(room);
    });
  });

});

// Emit a message every second
function send() {
  io.sockets.in('room').emit('data', 'howdy');
}

setInterval(send, 1000);

Et sur le navigateur...

// on the client
socket = io.connect();
socket.emit('join', ['room']);

socket.on('data', function(data){
  console.log(data);
});

le problème: chaque seconde, je suis réception de quatre messages, en raison de quatre processus de travail distincts envoi des messages.

Comment puis-je m'assurer que le message n'est envoyé qu'une fois?

54
demandé sur hexacyanide 2013-08-19 13:35:47

4 réponses

Edit: In Socket.IO 1.0+, plutôt que de configurer un magasin avec plusieurs clients Redis, un module adaptateur Redis plus simple peut maintenant être utilisé.

var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));

l'exemple ci-dessous ressemble plus à ceci:

var cluster = require('cluster');
var os = require('os');

if (cluster.isMaster) {
  // we create a HTTP server, but we do not use listen
  // that way, we have a socket.io server that doesn't accept connections
  var server = require('http').createServer();
  var io = require('socket.io').listen(server);
  var redis = require('socket.io-redis');

  io.adapter(redis({ host: 'localhost', port: 6379 }));

  setInterval(function() {
    // all workers will receive this in Redis, and emit
    io.emit('data', 'payload');
  }, 1000);

  for (var i = 0; i < os.cpus().length; i++) {
    cluster.fork();
  }

  cluster.on('exit', function(worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
  }); 
}

if (cluster.isWorker) {
  var express = require('express');
  var app = express();

  var http = require('http');
  var server = http.createServer(app);
  var io = require('socket.io').listen(server);
  var redis = require('socket.io-redis');

  io.adapter(redis({ host: 'localhost', port: 6379 }));
  io.on('connection', function(socket) {
    socket.emit('data', 'connected to worker: ' + cluster.worker.id);
  });

  app.listen(80);
}

si vous avez un noeud maître qui doit être publié sur une autre Socket.IO traite, mais n'accepte pas les connexions socket lui-même, utiliser socket.io-emitter au lieu de socket.io-redis .

si vous avez des problèmes d'échelle, lancez vos applications nodales avec DEBUG=* . Socket.IO implémente maintenant debug qui imprimera également les messages de debug de L'adaptateur Redis. Exemple de sortie:

socket.io:server initializing namespace / +0ms
socket.io:server creating engine.io instance with opts {"path":"/socket.io"} +2ms
socket.io:server attaching client serving req handler +2ms
socket.io-parser encoding packet {"type":2,"data":["event","payload"],"nsp":"/"} +0ms
socket.io-parser encoded {"type":2,"data":["event","payload"],"nsp":"/"} as 2["event","payload"] +1ms
socket.io-redis ignore same uid +0ms

si vos processus maître et enfant affichent les mêmes messages d'analyseur, alors votre application est correctement mise à l'échelle.


il ne devrait pas y avoir de problème avec votre installation si vous émettez d'un seul travailleur. Ce que vous faites est l'émission des quatre travailleurs, et en raison de Redis publish/subscribe, les messages ne sont pas dupliqués, mais écrit quatre fois, comme vous avez demandé à l'application de le faire. Voici un schéma simple de ce que fait Redis:

Client  <--  Worker 1 emit -->  Redis
Client  <--  Worker 2  <----------|
Client  <--  Worker 3  <----------|
Client  <--  Worker 4  <----------|

comme vous pouvez le voir, lorsque vous émettez d'un travailleur, il publiera l'émission à Redis, et il sera miroir d'autres travailleurs, qui ont souscrit à le Redis. Cela signifie également que vous pouvez utiliser plusieurs serveurs socket connectés la même instance, et une émission sur un serveur sera lancée sur tous les serveurs connectés.

avec cluster, quand un client se connecte, il se connecte à l'un de vos quatre travailleurs, pas tous les quatre. Cela signifie également que tout ce que vous émettez de ce travailleur ne sera montré qu'une seule fois au client. Donc, oui, l'application est mise à l'échelle, mais la façon dont vous le faites, vous émettez de tous quatre travailleurs, et la base de données Redis le fait comme si vous l'appeliez quatre fois sur un seul travailleur. Si un client se connectait aux quatre instances de votre socket, il recevrait seize messages par seconde, pas quatre.

le type de manipulation de la prise dépend du type d'application que vous allez avoir. Si vous allez gérer les clients individuellement, alors vous ne devriez pas avoir de problème, parce que l'événement de connexion ne tirera que pour un travailleur par un client. Si vous avez besoin d'un "heartbeat" global, alors vous pouvez avoir un gestionnaire de socket dans votre processus maître. Puisque les travailleurs meurent lorsque le processus maître meurt, vous devez compenser la charge de connexion du processus maître, et laisser les enfants manipuler les connexions. Voici un exemple:

var cluster = require('cluster');
var os = require('os');

if (cluster.isMaster) {
  // we create a HTTP server, but we do not use listen
  // that way, we have a socket.io server that doesn't accept connections
  var server = require('http').createServer();
  var io = require('socket.io').listen(server);

  var RedisStore = require('socket.io/lib/stores/redis');
  var redis = require('socket.io/node_modules/redis');

  io.set('store', new RedisStore({
    redisPub: redis.createClient(),
    redisSub: redis.createClient(),
    redisClient: redis.createClient()
  }));

  setInterval(function() {
    // all workers will receive this in Redis, and emit
    io.sockets.emit('data', 'payload');
  }, 1000);

  for (var i = 0; i < os.cpus().length; i++) {
    cluster.fork();
  }

  cluster.on('exit', function(worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
  }); 
}

if (cluster.isWorker) {
  var express = require('express');
  var app = express();

  var http = require('http');
  var server = http.createServer(app);
  var io = require('socket.io').listen(server);

  var RedisStore = require('socket.io/lib/stores/redis');
  var redis = require('socket.io/node_modules/redis');

  io.set('store', new RedisStore({
    redisPub: redis.createClient(),
    redisSub: redis.createClient(),
    redisClient: redis.createClient()
  }));

  io.sockets.on('connection', function(socket) {
    socket.emit('data', 'connected to worker: ' + cluster.worker.id);
  });

  app.listen(80);
}

dans l'exemple, il y a cinq douilles.IO exemples, un étant le maître, et quatre étant les enfants. Le serveur maître n'appelle jamais listen() il n'y a pas de connexion au-dessus sur ce processus. Cependant, si vous appelez un emit sur le processus maître, il sera publié dans le Redis, et les quatre processus travailleurs exécuteront l'emit sur leurs clients. Cela compense la charge de connexion aux travailleurs, et si un travailleur devait mourir, votre logique d'application principale serait intacte dans le maître.

notez qu'avec Redis, toutes les émissions, même dans un namespace ou une pièce, seront traitées par d'autres processus de travail comme si vous aviez déclenché l'émission de ce processus. Dans autrement dit, si vous avez deux prises.IO instances avec une instance Redis, appelant emit() sur une socket dans le premier travailleur enverra les données à ses clients, tandis que worker deux fera la même chose que si vous avez appelé l'emit de ce travailleur.

83
répondu hexacyanide 2015-06-20 19:16:00

laisser le maître gérer votre rythme cardiaque (exemple ci-dessous) ou démarrer plusieurs processus sur différents ports en interne et les équilibrer avec nginx (qui supporte également les websockets de V1.3 vers le haut).

Cluster avec Maître

// on the server
var express = require('express');
var server = express();
var socket = require('socket.io');
var io = socket.listen(server);
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

// socket.io
io.set('store', new socket.RedisStore);

// set-up connections...
io.sockets.on('connection', function(socket) {
    socket.on('join', function(rooms) {
        rooms.forEach(function(room) {
            socket.join(room);
        });
    });

    socket.on('leave', function(rooms) {
        rooms.forEach(function(room) {
            socket.leave(room);
        });
    });

});

if (cluster.isMaster) {
    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    // Emit a message every second
    function send() {
        console.log('howdy');
        io.sockets.in('room').emit('data', 'howdy');
    }

    setInterval(send, 1000);


    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    }); 
}
2
répondu Taner Topal 2013-09-03 11:33:41

ça ressemble à une prise.Je réussis à changer d'échelle. Vous vous attendez à ce qu'un message d'un serveur aille à toutes les sockets de cette pièce, quel que soit le serveur auquel ils sont connectés.

Votre meilleur pari est d'avoir un processus maître qui envoie un message à chaque seconde. Vous pouvez le faire en l'exécutant seulement si cluster.isMaster , par exemple.

1
répondu Aaron Dufour 2013-09-01 02:15:27

la communication inter-processus n'est pas suffisante pour faire une socket.io 1.4.5 travailler avec cluster. Forcer le mode websocket est également un must. Voir WebSocket poignée de main au Nœud.Js, Socket.IO et Clusters ne travaillant pas

0
répondu gdorbes 2017-05-23 12:18:17