sql >> Databáze >  >> NoSQL >> Redis

Škálování Socket.IO na více procesů Node.js pomocí clusteru

Upravit: V Socket.IO 1.0+ lze nyní namísto nastavování úložiště s více klienty Redis použít jednodušší modul adaptéru Redis.

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

Níže uvedený příklad by vypadal spíše takto:

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);
}

Pokud máte hlavní uzel, který potřebuje publikovat do jiných procesů Socket.IO, ale sám nepřijímá připojení soketu, použijte místo socket.io-redis socket.io-emitter.

Pokud máte problémy se škálováním, spouštějte aplikace Node s DEBUG=* . Socket.IO nyní implementuje ladění, které také vytiskne zprávy o ladění adaptéru Redis. Příklad výstupu:

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

Pokud váš hlavní i podřízený proces zobrazují stejné zprávy analyzátoru, pak se vaše aplikace správně škáluje.

Pokud vysíláte od jednoho pracovníka, neměl by být problém s nastavením. To, co děláte, je vysílání od všech čtyř pracovníků a díky publikování/odběru Redis se zprávy neduplikují, ale píší čtyřikrát, jak jste požádali aplikaci. Zde je jednoduchý diagram toho, co Redis dělá:

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

Jak můžete vidět, když odešlete od pracovníka, publikuje to do Redis a bude zrcadleno od ostatních pracovníků, kteří se přihlásili k databázi Redis. To také znamená, že můžete použít více soketových serverů připojených ke stejné instanci a emitování na jednom serveru bude spuštěno na všech připojených serverech.

S clusterem, když se klient připojí, připojí se k jednomu z vašich čtyř pracovníků, ne ke všem čtyřem. To také znamená, že vše, co od tohoto pracovníka vydáte, se klientovi zobrazí pouze jednou. Takže ano, aplikace se škáluje, ale způsob, jakým to děláte, vysíláte od všech čtyř pracovníků a databáze Redis to dělá, jako byste to zavolali čtyřikrát na jednoho pracovníka. Pokud by se klient skutečně připojil ke všem čtyřem instancím vašeho soketu, obdržel by šestnáct zpráv za sekundu, nikoli čtyři.

Typ zpracování soketu závisí na typu aplikace, kterou budete mít. Pokud budete s klienty pracovat individuálně, pak byste neměli mít žádný problém, protože událost připojení se spustí pouze pro jednoho pracovníka na jednoho klienta. Pokud potřebujete globální „tep srdce“, můžete mít ve svém hlavním procesu obslužný program soketu. Vzhledem k tomu, že pracovníci zemřou, když hlavní proces zemře, měli byste kompenzovat zatížení připojení hlavního procesu a nechat připojení zvládnout děti. Zde je příklad:

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);
}

V příkladu je pět instancí Socket.IO, jedna je hlavní a čtyři jsou potomky. Hlavní server nikdy nevolá listen() takže v tomto procesu není žádná režie připojení. Pokud však zavoláte vysílání na hlavním procesu, bude publikováno do Redis a čtyři pracovní procesy provedou vysílání na svých klientech. Toto kompenzuje zatížení připojení k pracovníkům, a pokud by pracovník zemřel, vaše hlavní aplikační logika by zůstala v hlavním systému nedotčena.

Všimněte si, že s Redis budou všechna vysílání, dokonce i ve jmenném prostoru nebo místnosti, zpracována jinými pracovními procesy, jako byste spustili vysílání z tohoto procesu. Jinými slovy, pokud máte dvě instance Socket.IO s jednou instancí Redis, volání emit() na soketu v prvním workeru odešle data svým klientům, zatímco worker dva udělá to samé, jako kdybyste zavolali emit od tohoto workera.



  1. Chyba přetypování JSON.NET při serializaci Mongo ObjectId

  2. Úvod do datových typů MongoDB

  3. Chcete používat Redis jako úložiště statistik událostí

  4. ImportError:Žádný modul s názvem redis