Edytuj: W Socket.IO 1.0+ zamiast konfigurować sklep z wieloma klientami Redis, można teraz użyć prostszego modułu adaptera Redis.
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
Poniższy przykład wyglądałby bardziej tak:
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);
}
Jeśli masz węzeł główny, który musi publikować w innych procesach Socket.IO, ale sam nie akceptuje połączeń z gniazdem, użyj socket.io-emitter zamiast socket.io-redis.
Jeśli masz problemy ze skalowaniem, uruchom aplikacje Node z DEBUG=*
. Socket.IO implementuje teraz debugowanie, które wyświetla również komunikaty debugowania adaptera Redis. Przykładowe wyjście:
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
Jeśli zarówno proces nadrzędny, jak i podrzędny wyświetlają te same komunikaty parsera, oznacza to, że aplikacja jest odpowiednio skalowana.
Nie powinno być problemu z twoją konfiguracją, jeśli emitujesz z jednego pracownika. To, co robisz, emituje ze wszystkich czterech procesów roboczych, a ze względu na publikowanie/subskrypcję Redis wiadomości nie są duplikowane, ale zapisywane czterokrotnie, tak jak prosiłeś o to aplikację. Oto prosty diagram tego, co robi Redis:
Client <-- Worker 1 emit --> Redis
Client <-- Worker 2 <----------|
Client <-- Worker 3 <----------|
Client <-- Worker 4 <----------|
Jak widać, gdy emitujesz od pracownika, opublikuje on emisję do Redis i będzie dublowany od innych procesów roboczych, które subskrybują bazę danych Redis. Oznacza to również, że możesz używać wielu serwerów gniazd podłączonych do tej samej instancji, a emisja na jednym serwerze zostanie uruchomiona na wszystkich podłączonych serwerach.
W przypadku klastra, gdy klient połączy się, połączy się z jednym z czterech pracowników, a nie wszystkimi czterema. Oznacza to również, że wszystko, co wyemitujesz od tego pracownika, zostanie pokazane klientowi tylko raz. Więc tak, aplikacja się skaluje, ale sposób, w jaki to robisz, emituje ze wszystkich czterech procesów roboczych, a baza danych Redis sprawia, że jest to wywoływane cztery razy na jednym procesie roboczym. Jeśli klient faktycznie połączyłby się ze wszystkimi czterema instancjami gniazd, otrzymywałby szesnaście wiadomości na sekundę, a nie cztery.
Rodzaj obsługi gniazd zależy od typu aplikacji, którą będziesz mieć. Jeśli zamierzasz obsługiwać klientów indywidualnie, nie powinieneś mieć problemu, ponieważ zdarzenie połączenia zostanie uruchomione tylko dla jednego pracownika na jednego klienta. Jeśli potrzebujesz globalnego „bicia serca”, możesz mieć obsługę gniazd w swoim głównym procesie. Ponieważ pracownicy umierają, gdy umiera proces nadrzędny, należy odciążyć proces nadrzędny od obciążenia połączenia i pozwolić dzieciom obsługiwać połączenia. Oto przykład:
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);
}
W tym przykładzie istnieje pięć instancji Socket.IO, z których jedna jest wzorcem, a cztery są dziećmi. Serwer główny nigdy nie wywołuje funkcji listen()
więc nie ma narzutu na połączenie w tym procesie. Jeśli jednak wywołasz emisję w procesie głównym, zostanie ona opublikowana w Redis, a cztery procesy robocze wykonają emisję na swoich klientach. Powoduje to przesunięcie obciążenia połączenia na pracowników, a jeśli pracownik zginie, główna logika aplikacji pozostanie nienaruszona w systemie głównym.
Zwróć uwagę, że w przypadku Redis wszystkie emisje, nawet w przestrzeni nazw lub pokoju, będą przetwarzane przez inne procesy robocze, tak jakbyś wyzwolił emisję z tego procesu. Innymi słowy, jeśli masz dwie instancje Socket.IO z jedną instancją Redis, wywołując emit()
na gnieździe w pierwszym pracowniku wyśle dane do swoich klientów, podczas gdy drugi pracownik zrobi to samo, co wywołanie emisji od tego pracownika.