Bewerken: In Socket.IO 1.0+ kan nu, in plaats van een winkel met meerdere Redis-clients in te stellen, een eenvoudigere Redis-adaptermodule worden gebruikt.
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
Het onderstaande voorbeeld ziet er ongeveer zo uit:
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);
}
Als je een hoofdknooppunt hebt dat moet publiceren naar andere Socket.IO-processen, maar zelf geen socketverbindingen accepteert, gebruik dan socket.io-emitter in plaats van socket.io-redis.
Als u problemen ondervindt bij het schalen, voert u uw Node-toepassingen uit met DEBUG=*
. Socket.IO implementeert nu debug die ook Redis-adapterfoutopsporingsberichten zal afdrukken. Voorbeelduitvoer:
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
Als zowel uw hoofd- als onderliggende processen beide dezelfde parserberichten weergeven, dan is uw toepassing correct geschaald.
Er zou geen probleem moeten zijn met uw instellingen als u uitzendt door een enkele werknemer. Wat je doet, is het uitzenden van alle vier de werknemers, en dankzij Redis publish/subscribe worden de berichten niet gedupliceerd, maar vier keer geschreven, zoals je de toepassing hebt gevraagd. Hier is een eenvoudig diagram van wat Redis doet:
Client <-- Worker 1 emit --> Redis
Client <-- Worker 2 <----------|
Client <-- Worker 3 <----------|
Client <-- Worker 4 <----------|
Zoals u kunt zien, wanneer u uitzendt van een werknemer, zal deze de uitzending publiceren naar Redis, en het zal worden gespiegeld door andere werknemers die zich hebben geabonneerd op de Redis-database. Dit betekent ook dat je meerdere socketservers kunt gebruiken die op dezelfde instantie zijn aangesloten, en een uitzending op één server wordt geactiveerd op alle verbonden servers.
Met cluster, wanneer een client verbinding maakt, maakt deze verbinding met een van uw vier werknemers, niet met alle vier. Dat betekent ook dat alles wat u van die werknemer uitzendt, slechts één keer aan de klant wordt getoond. Dus ja, de applicatie schaalt, maar zoals je het doet, zend je uit van alle vier de werkers, en de Redis-database maakt het alsof je het vier keer aanroept op een enkele werker. Als een client daadwerkelijk verbinding zou maken met alle vier je socketinstanties, zouden ze zestien berichten per seconde ontvangen, niet vier.
Het type socketbehandeling hangt af van het type toepassing dat u gaat gebruiken. Als u clients afzonderlijk gaat afhandelen, zou u geen probleem moeten hebben, omdat de verbindingsgebeurtenis slechts voor één worker per client wordt geactiveerd. Als je een globale "hartslag" nodig hebt, zou je een socket-handler in je hoofdproces kunnen hebben. Aangezien werknemers sterven wanneer het hoofdproces sterft, moet u de verbindingsbelasting van het hoofdproces compenseren en de kinderen de verbindingen laten afhandelen. Hier is een voorbeeld:
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);
}
In het voorbeeld zijn er vijf Socket.IO-instanties, waarvan één de master is en vier de kinderen. De hoofdserver roept nooit listen()
. aan dus er is geen verbindingsoverhead op dat proces. Als u echter een emissie aanroept voor het hoofdproces, wordt deze gepubliceerd naar Redis en voeren de vier werkprocessen de emissie uit op hun clients. Dit compenseert de verbindingsbelasting voor werknemers, en als een werknemer zou overlijden, zou uw belangrijkste toepassingslogica onaangetast blijven in de master.
Houd er rekening mee dat met Redis alle uitzendingen, zelfs in een naamruimte of kamer, worden verwerkt door andere werkprocessen alsof u de uitzending vanuit dat proces hebt geactiveerd. Met andere woorden, als u twee Socket.IO-instanties hebt met één Redis-instantie, roept u emit()
aan op een socket in de eerste worker zal de data naar zijn clients sturen, terwijl worker twee hetzelfde zal doen alsof je de emit van die worker aanroept.