Edit: В Socket.io 1.0+, а не устанавливать магазин с несколькими клиентами Redis, простой модуль адаптера Redis теперь могут быть использованы.
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
В приведенном ниже примере будет выглядеть следующим образом:
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);
}
Если у вас есть главный узел, который должен опубликовать другие процессы Socket.io, но не принимает сокет соединения самостоятельно, используйте socket.io-emitter вместо socket.io-redis.
Если у вас возникли проблемы с масштабированием, запустите приложения Node с помощью DEBUG=*
. Socket.IO теперь реализует debug, который также будет распечатывать отладочные сообщения адаптера Redis. Пример вывод:
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
Если оба ваши процессы мастера и ребенок оба отображения тех же анализатор сообщений, то ваше приложение правильно масштабирование.
Неправильная установка вашей конфигурации, если вы испускаете одного работника. То, что вы делаете, исходит от всех четырех рабочих, и из-за публикации/подписки Redis сообщения не дублируются, а четыре раза записываются, как вы просили приложение сделать.Вот простая схема, что делает Redis:
Client <-- Worker 1 emit --> Redis
Client <-- Worker 2 <----------|
Client <-- Worker 3 <----------|
Client <-- Worker 4 <----------|
Как вы можете видеть, когда вы испускаете от работника, он будет издавать Emit к Redis, и оно будет отражено от других работников, которые подписались на База данных Redis. Это также означает, что вы можете использовать несколько серверов сокетов, связанных с одним и тем же экземпляром, и на всех подключенных серверах будет запущен источник на одном сервере.
С кластером, когда клиент подключается, он подключится к одному из ваших четырех рабочих, а не ко всем четырем. Это также означает, что все, что вы испускаете от этого работника, будет отображаться только одному клиенту. Итак, да, приложение масштабируется, но, как вы это делаете, вы испускаете всех четырех рабочих, а база данных Redis делает это так, как если бы вы звонили ему четыре раза на одного работника. Если клиент действительно подключился ко всем четырем экземплярам вашего сокета, они получат шестнадцать сообщений в секунду, а не четыре.
Тип обработки сокетов зависит от типа приложения, которое у вас будет. Если вы собираетесь обрабатывать клиентов по отдельности, тогда у вас не должно быть проблем, потому что событие соединения будет срабатывать только для одного рабочего на одного клиента. Если вам нужно глобальное «сердцебиение», тогда у вас может быть обработчик сокета в вашем основном процессе. Поскольку работники умирают, когда мастер-процесс умирает, вы должны компенсировать нагрузку на соединение основного процесса и позволить дочерним элементам обрабатывать соединения. Вот пример:
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);
}
В примере, есть пять Socket.io экземпляров, один из которых мастер, и четыре являются дети. Главный сервер никогда не вызывает listen()
, поэтому на этот процесс нет никаких дополнительных затрат на соединение. Однако, если вы вызываете emit в основном процессе, он будет опубликован в Redis, и четыре рабочих процесса будут выполнять emit на своих клиентах. Это компенсирует нагрузку на соединение для рабочих, и если работник должен умереть, ваша основная логика приложения будет не затронута в мастер.
Обратите внимание, что при использовании Redis все испускаемые объекты даже в пространстве имен или в помещении будут обрабатываться другими рабочими процессами, как если бы вы инициировали излучение из этого процесса. Другими словами, если у вас есть два экземпляра Socket.IO с одним экземпляром Redis, вызов emit()
в сокете первого работника отправит данные своим клиентам, а работники-двое будут делать то же, что и если вы вызвали излучение от этого рабочего ,
Какую версию socket.io вы используете? Socket.IO 0.6 разработан как один сервер процессов. Обратите внимание на ответ 3rdEden в этой статье stackoverflow. http://stackoverflow.com/questions/5944714/how-can-i-scale-socket-io – HariKrishnan
0.9.16 с помощью RedisStore –
Вы можете использовать SocketCluster (интерфейс сокета совместим с Socket.io): https: // github.com/topcloud/socketcluster – Jon