IT TIP

클러스터를 사용하여 Socket.IO를 여러 Node.js 프로세스로 확장

itqueen 2020. 12. 4. 21:39
반응형

클러스터를 사용하여 Socket.IO를 여러 Node.js 프로세스로 확장


이걸로 내 머리카락을 찢어 버리는 중 ... 누구든지 Socket.IO 를 Node.js의 클러스터 모듈에 의해 생성 된 여러 "작업자"프로세스 로 확장 할 수 있었습니까?

4 개의 작업자 프로세스 (의사) 에 다음이 있다고 가정 해 보겠습니다 .

// on the server
var express = require('express');
var server = express();
var socket = require('socket.io');
var io = socket.listen(server);

// socket.io
io.set('store', new socket.RedisStore);

// set-up connections...
io.sockets.on('connection', function(socket) {

  socket.on('join', function(rooms) {
    rooms.forEach(function(room) {
      socket.join(room);
    });
  });

  socket.on('leave', function(rooms) {
    rooms.forEach(function(room) {
      socket.leave(room);
    });
  });

});

// Emit a message every second
function send() {
  io.sockets.in('room').emit('data', 'howdy');
}

setInterval(send, 1000);

그리고 브라우저에서 ...

// on the client
socket = io.connect();
socket.emit('join', ['room']);

socket.on('data', function(data){
  console.log(data);
});

문제 : 메시지를 보내는 4 개의 개별 작업자 프로세스로 인해 매초마다 4 개의 메시지가 수신 됩니다.

메시지가 한 번만 전송되도록하려면 어떻게합니까?


편집 : 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-redis 대신 socket.io-emitter사용 하십시오 .

확장하는 데 문제가있는 경우 DEBUG=*. Socket.IO는 이제 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  <----------|

보시다시피 워커에서 방출하면 방출을 Redis에 게시하고 Redis 데이터베이스에 가입 한 다른 워커에서 미러링합니다. 이것은 또한 동일한 인스턴스에 연결된 여러 소켓 서버를 사용할 수 있으며 한 서버의 방출이 연결된 모든 서버에서 실행된다는 것을 의미합니다.

클러스터를 사용하면 클라이언트가 연결될 때 4 개 모두가 아닌 4 개 작업자 중 하나에 연결됩니다. 이는 또한 해당 작업자에서 방출하는 모든 것이 클라이언트에게 한 번만 표시된다는 것을 의미합니다. 예, 애플리케이션이 확장되고 있지만 수행하는 방식에 따라 4 명의 작업자 모두에서 내보내고 Redis 데이터베이스는 단일 작업자에서 4 번 호출하는 것처럼 만듭니다. 클라이언트가 실제로 4 개의 소켓 인스턴스에 모두 연결되어 있다면 4 개가 아닌 16 개의 메시지를 1 초에 수신하게됩니다.

소켓 처리 유형은 사용할 애플리케이션 유형에 따라 다릅니다. 클라이언트를 개별적으로 처리하려는 경우 연결 이벤트가 클라이언트 당 하나의 작업자에 대해서만 발생하므로 문제가 없습니다. 전역 "하트 비트"가 필요한 경우 마스터 프로세스에 소켓 처리기가있을 수 있습니다. 작업자는 마스터 프로세스가 죽으면 죽기 때문에 마스터 프로세스의 연결 부하를 상쇄하고 자식이 연결을 처리하도록해야합니다. 예를 들면 다음과 같습니다.

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 the example, there are five Socket.IO instances, one being the master, and four being the children. The master server never calls listen() so there is no connection overhead on that process. However, if you call an emit on the master process, it will be published to Redis, and the four worker processes will perform the emit on their clients. This offsets connection load to workers, and if a worker were to die, your main application logic would be untouched in the master.

Note that with Redis, all emits, even in a namespace or room will be processed by other worker processes as if you triggered the emit from that process. In other words, if you have two Socket.IO instances with one Redis instance, calling emit() on a socket in the first worker will send the data to its clients, while worker two will do the same as if you called the emit from that worker.


Let the master handle your heartbeat (example below) or start multiple processes on different ports internally and load balance them with nginx (which supports also websockets from V1.3 upwards).

Cluster with Master

// on the server
var express = require('express');
var server = express();
var socket = require('socket.io');
var io = socket.listen(server);
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;

// socket.io
io.set('store', new socket.RedisStore);

// set-up connections...
io.sockets.on('connection', function(socket) {
    socket.on('join', function(rooms) {
        rooms.forEach(function(room) {
            socket.join(room);
        });
    });

    socket.on('leave', function(rooms) {
        rooms.forEach(function(room) {
            socket.leave(room);
        });
    });

});

if (cluster.isMaster) {
    // Fork workers.
    for (var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }

    // Emit a message every second
    function send() {
        console.log('howdy');
        io.sockets.in('room').emit('data', 'howdy');
    }

    setInterval(send, 1000);


    cluster.on('exit', function(worker, code, signal) {
        console.log('worker ' + worker.process.pid + ' died');
    }); 
}

This actually looks like Socket.IO succeeding at scaling. You would expect a message from one server to go to all sockets in that room, regardless of which server they happen to be connected to.

Your best bet is to have one master process that sends a message each second. You can do this by only running it if cluster.isMaster, for example.


Inter-process communication is not enough to make socket.io 1.4.5 working with cluster. Forcing websocket mode is also a must. See WebSocket handshake in Node.JS, Socket.IO and Clusters not working

참고URL : https://stackoverflow.com/questions/18310635/scaling-socket-io-to-multiple-node-js-processes-using-cluster

반응형