sql >> Databasteknik >  >> NoSQL >> Redis

Skala Socket.IO till flera Node.js-processer med hjälp av kluster

Redigera: I Socket.IO 1.0+, istället för att ställa in en butik med flera Redis-klienter, kan nu en enklare Redis-adaptermodul användas.

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

Exemplet nedan skulle se mer ut så här:

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

Om du har en masternod som behöver publicera till andra Socket.IO-processer, men som inte accepterar socketanslutningar själv, använd socket.io-emitter istället för socket.io-redis.

Om du har problem med att skala, kör dina Node-applikationer med DEBUG=* . Socket.IO implementerar nu felsökning som också kommer att skriva ut Redis-adapterfelsökningsmeddelanden. Exempelutgång:

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

Om både din huvud- och din underordnade process visar samma parsermeddelanden, är din applikation korrekt skalad.

Det borde inte vara några problem med din installation om du emitterar från en enskild arbetare. Det du gör är att sända ut från alla fyra arbetarna, och på grund av Redis publicera/prenumerera, dupliceras inte meddelandena, utan skrivs fyra gånger, som du bad applikationen att göra. Här är ett enkelt diagram över vad Redis gör:

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

Som du kan se, när du sänder ut från en arbetare, kommer den att publicera utsändningen till Redis, och den kommer att speglas från andra arbetare som har prenumererat på Redis-databasen. Detta innebär också att du kan använda flera socketservrar anslutna till samma instans, och en emit på en server kommer att avfyras på alla anslutna servrar.

Med kluster, när en klient ansluter, kommer den att ansluta till en av dina fyra arbetare, inte alla fyra. Det betyder också att allt du sänder ut från den arbetaren endast kommer att visas en gång för kunden. Så ja, applikationen skalas, men som du gör det sänder du ut från alla fyra arbetarna, och Redis-databasen gör det som om du anropade den fyra gånger på en enskild arbetare. Om en klient faktiskt ansluter till alla fyra av dina socket-instanser, skulle de få sexton meddelanden i sekunden, inte fyra.

Typen av uttagshantering beror på vilken typ av applikation du ska ha. Om du ska hantera klienter individuellt bör du inte ha några problem, eftersom anslutningshändelsen bara aktiveras för en arbetare per klient. Om du behöver ett globalt "hjärtslag", kan du ha en sockethanterare i din masterprocess. Eftersom arbetare dör när huvudprocessen dör, bör du kompensera anslutningsbelastningen från huvudprocessen och låta barnen hantera anslutningar. Här är ett exempel:

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

I exemplet finns det fem Socket.IO-instanser, en är master och fyra är barn. Huvudservern anropar aldrig listen() så det finns ingen kopplingsoverhead på den processen. Men om du anropar en emit på masterprocessen kommer den att publiceras till Redis, och de fyra arbetsprocesserna kommer att utföra emit på sina kunder. Detta kompenserar anslutningsbelastningen för arbetare, och om en arbetare skulle dö, skulle din huvudapplikationslogik vara orörd i mastern.

Observera att med Redis kommer alla utsändningar, även i ett namnområde eller rum, att behandlas av andra arbetsprocesser som om du utlöste utsändningen från den processen. Med andra ord, om du har två Socket.IO-instanser med en Redis-instans, anropar du emit() på en socket i den första arbetaren kommer att skicka data till sina kunder, medan arbetare två kommer att göra samma sak som om du anropade emit från den arbetaren.



  1. MongoDB $dateToString

  2. Så här gör du:Indexera data från S3 med CDP Data Hub

  3. Bra sätt att använda socket.io med kluster i multi-core server?

  4. Hur förbättrar jag Redis-serverns CPU-användning?