sql >> Database teknologi >  >> NoSQL >> Redis

Skalering af Socket.IO til flere Node.js-processer ved hjælp af klynge

Rediger: I Socket.IO 1.0+, i stedet for at oprette en butik med flere Redis-klienter, kan et enklere Redis-adaptermodul nu bruges.

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

Eksemplet nedenfor ville se mere sådan ud:

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

Hvis du har en masternode, der skal publicere til andre Socket.IO-processer, men ikke selv accepterer socketforbindelser, skal du bruge socket.io-emitter i stedet for socket.io-redis.

Hvis du har problemer med at skalere, skal du køre dine Node-applikationer med DEBUG=* . Socket.IO implementerer nu debug, som også vil udskrive Redis-adapter-fejlretningsmeddelelser. Eksempel output:

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

Hvis både din master- og din underordnede processer begge viser de samme parser-meddelelser, er din applikation korrekt skaleret.

Der burde ikke være et problem med din opsætning, hvis du udsender fra en enkelt arbejder. Det, du laver, er at udsende fra alle fire arbejdere, og på grund af Redis publicering/subscribe bliver beskederne ikke duplikeret, men skrevet fire gange, som du bad applikationen om at gøre. Her er et simpelt diagram over, hvad Redis gør:

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

Som du kan se, når du udsender fra en arbejder, vil den offentliggøre udsendelsen til Redis, og den vil blive spejlet fra andre arbejdere, som har abonneret på Redis-databasen. Dette betyder også, at du kan bruge flere socket-servere, der er tilsluttet den samme instans, og en emit på én server vil blive affyret på alle tilsluttede servere.

Med cluster, når en klient opretter forbindelse, vil den oprette forbindelse til en af ​​dine fire arbejdere, ikke alle fire. Det betyder også, at alt, hvad du udsender fra den pågældende arbejder, kun vil blive vist én gang til kunden. Så ja, applikationen skalerer, men som du gør det, udsender du fra alle fire arbejdere, og Redis-databasen gør det, som om du kalder det fire gange på en enkelt arbejder. Hvis en klient rent faktisk sluttede sig til alle fire af dine socket-instanser, ville de modtage seksten beskeder i sekundet, ikke fire.

Typen af ​​socket-håndtering afhænger af den type applikation, du skal have. Hvis du skal håndtere klienter individuelt, så skulle du ikke have noget problem, fordi forbindelseshændelsen kun udløses for én arbejder pr. klient. Hvis du har brug for et globalt "hjerteslag", så kan du have en socket-handler i din masterproces. Da arbejdere dør, når masterprocessen dør, bør du udligne forbindelsesbelastningen fra masterprocessen og lade børnene håndtere forbindelser. Her er et eksempel:

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 eksemplet er der fem Socket.IO-instanser, hvor en er masteren og fire er børnene. Masterserveren kalder aldrig listen() så der er ingen forbindelse overhead på den proces. Men hvis du kalder en emit på masterprocessen, vil den blive offentliggjort til Redis, og de fire arbejdsprocesser vil udføre emit på deres klienter. Dette udligner forbindelsesbelastningen til arbejdere, og hvis en arbejder skulle dø, ville din hovedapplikationslogik være uberørt i masteren.

Bemærk, at med Redis vil alle udsendelser, selv i et navneområde eller rum, blive behandlet af andre arbejdsprocesser, som om du udløste udsendelsen fra denne proces. Med andre ord, hvis du har to Socket.IO-instanser med én Redis-instans, kalder du emit() på en socket i den første arbejder vil sende data til sine klienter, mens arbejder to vil gøre det samme, som hvis du kaldte udsenderen fra den pågældende arbejder.



  1. nodejs mongodb objekt-id til streng

  2. Percona Live 2017 - Severalnines Recap

  3. Cluster Failover

  4. Django-nonrel vs Django-mongodb vs Mongokit vs pymongo native