60 votes

Mise à l'échelle de Socket.io à plusieurs clusters de nœuds

Déchirer mes cheveux avec celui-ci... quelqu'un a réussi à l'échelle de la prise.les e / s multiples de "travailleur" processus engendré par Node.js s'Cluster module?

Disons que j'ai le texte suivant sur 4 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 dans 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 reçois QUATRE messages... en raison de 4 séparer les processus de travail de l'envoi des messages.

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

96voto

hexacyanide Points 15723

Il ne devrait pas être un problème avec votre configuration si vous êtes émettant à partir d'un seul travailleur. Ce que vous faites est émise par les quatre travailleurs, et en raison de Redis publier/souscrire, les messages ne sont pas dupliquées, mais écrit à quatre reprises, comme vous l'avez demandé l'application à réaliser. Voici un schéma simplifié de ce qui ne Redis:

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

Comme vous pouvez le voir, lorsque vous émettez à partir d'un travailleur, il publiera le émettent à Redis, et il sera mis en miroir des autres travailleurs, qui ont souscrit à le Redis. Cela signifie également que vous pouvez utiliser de multiples serveurs socket connecté de la même instance, et un émettent sur un serveur sera tiré sur tous les serveurs connectés.

Avec cluster, lorsqu'un client se connecte, il va se connecter à l'un de vos quatre travailleurs, pas tous les quatre. Cela signifie également que tout ce que vous émettez à partir de ce travailleur ne sera affiché une fois pour le client. Donc oui, l'application est mise à l'échelle, mais la façon dont vous le faites, vous êtes en émettant des quatre travailleurs, et le Redis, il est comme si vous étiez en appelant à quatre reprises sur un seul travailleur. Si un client connecté à tous les quatre de vos socket cas, ils seraient réception de seize messages par seconde, et non pas quatre.

Le type de socket de la manipulation dépend du type d'application que vous allez avoir. Si vous allez gérer les clients individuellement, alors vous devriez avoir aucun problème, parce que la connexion de l'événement se déclenche uniquement pour un travailleur par un client. Si vous avez besoin d'un mondial "battement de coeur", alors vous pourriez avoir un socket gestionnaire dans votre processus maître. Depuis le décès de travailleurs lorsque le processus maître meurt, vous devez compenser la connexion de charge sur le processus maître, et laissez les enfants à gérer 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 fs = require('fs');

  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 Socket.IO instances, l'une étant le maître, et quatre enfants. Le serveur maître n'appelle jamais listen() donc il n'y a pas de connexion au-dessus sur ce processus. Toutefois, si vous appelez un émettent sur le processus maître, il sera publié à Redis, et les quatre processus de travail va effectuer le émettent sur leurs clients. Cela compense la connexion de la charge des travailleurs, et si un travailleur est de mourir, votre principale de l'application de la logique serait pas abordée dans le maître.

Notez qu'avec le Redis, tous les émet, même dans un espace de noms ou de la salle seront traitées par d'autres processus de travail, comme si vous déclenchée le émettent à partir de ce processus. En d'autres termes, si vous avez deux Prise.IO instances avec un Redis instance, appel emit() sur un socket dans le premier travailleur va envoyer les données de ses clients, tandis que les travailleurs des deux fera la même chose que si vous l'avez appelé le émettent à partir de ce travailleur.

2voto

Taner Topal Points 519

Laissez le maître gérer votre rythme cardiaque (exemple ci-dessous) ou démarrez plusieurs processus sur différents ports en interne et équilibrez leur charge avec nginx (qui prend également en charge les websockets à partir de la V1.3).

Cluster avec le 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');
    }); 
}
 

1voto

Aaron Dufour Points 6912

Cela ressemble en fait à Socket.IO qui réussit à mettre à l'échelle. Vous vous attendriez à ce qu'un message d'un serveur parvienne à tous 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 ne l'exécutant que si cluster.isMaster , par exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X