node.jssocket.iosocket.io-redisvue-socket.io

Socket.io not working when using multiple nodes through pm2


I have NodeJS application running with Socket.io for real-time updates. I am using PM2 process manager for production Node.js application. Now I want to use cluster mode in PM2. As nodejs applications runs in single process, I want to utilize max cpu available in on my server system. Currently I have 4 cores in my system. So with PM2 cluster mode, i can utilize all cores and PM2 will handle everything on its own behind the scene.

When using single server instance, My nodejs app with socket.io working fine with client. But when i am using cluster mode, PM2 launches 4 server instances. I tried to connect multiple client (through opening serveral terminals and running client side part on them) and they are connecting successfully with instances launched by PM2 in random manner which is expected and fine.

What i want to do?

  1. Initiate the pm2 with cluster mode
  2. clients can connect with any of the launched instance by pm2
  3. after client connects, Server-1 will emit the events which should be sent to all the clients connected to all server
  4. After successfully testing above steps, i will integrate the logic of room, but as i am not getting success in step 3, I am not going for setp 4.

My problem is, when i want to emit event from server-1 to clients, it should be sent to all clients connected to all 4 server instance (server-1, server-2, server-3, server-4 instances launched by PM2).

But whenever i am emiting event from server-1, it only gets sent to clients connected to server-1 only.

and i get error in server logs,

You have triggered an unhandledRejection, you may have forgotten to catch a Promise rejection:

Error: The client is closed
    at Commander._RedisClient_sendCommand (/var/www/html/test/server-socket/node_modules/@node-redis/client/dist/lib/client/index.js:387:31)
    at Commander.commandsExecutor (/var/www/html/test/server-socket/node_modules/@node-redis/client/dist/lib/client/index.js:160:154)
    at Commander.BaseClass.<computed> [as publish] (/var/www/html/test/server-socket/node_modules/@node-redis/client/dist/lib/commander.js:8:29)
    at RedisAdapter.broadcast (/var/www/html/test/server-socket/node_modules/@socket.io/redis-adapter/dist/index.js:374:28)
    at BroadcastOperator.emit (/var/www/html/test/server-socket/node_modules/socket.io/dist/broadcast-operator.js:109:22)
    at Namespace.emit (/var/www/html/test/server-socket/node_modules/socket.io/dist/namespace.js:170:73)
    at Server.<computed> [as emit] (/var/www/html/test/server-socket/node_modules/socket.io/dist/index.js:576:33)
    at Timeout._onTimeout (/var/www/html/test/server-socket/index.js:25:16)
    at listOnTimeout (internal/timers.js:549:17)
    at processTimers (internal/timers.js:492:7)

index.js (Server side)

const { Server } = require("socket.io")
const { createAdapter } = require("@socket.io/redis-adapter");
const { createClient } = require("redis");

const io = new Server({ transports: ['websocket'] })

const pubClient = createClient({ host: 'localhost', port: 6379, auth_pass: "root" });
const subClient = pubClient.duplicate();
subClient.psubscribe = pubClient.pSubscribe

io.adapter(createAdapter(pubClient, subClient))
io.listen(3000)

// console.log('process.env.NODE_APP_INSTANCE: ' + process.env.NODE_APP_INSTANCE);

io.on("connection", (socket) => {
    console.log(socket.id + ' connected on server: ' + process.env.NODE_APP_INSTANCE)

    // emitting event from only server-1 to test whether all client gets it or not
    if(process.env.NODE_APP_INSTANCE == 0){
        setInterval(() => {
            io.emit('test_msg', 'server: ' + process.env.NODE_APP_INSTANCE)
        }, 5000)
    }
});

index.js (At client side)

const { io } = require("socket.io-client");
const socket = io("http://localhost:3000", {
    transports: ["websocket"]
});

socket.on("connect", () => {
    console.log(socket.id + ' connected with server')
})

socket.on("test_msg", (data) => {
    console.log('test_msg caught: ' + data)
})

ecosystem.config.js file (at server side for pm2)

module.exports = {
  apps : [{
    name: 'server-socket',
    script: 'index.js',
    watch: '.',
    // instances  : "max",
    instances  : 4,
    exec_mode  : "cluster",
    increment_var : 'PORT',
    env_development: {
      PORT: 3000,
      NODE_ENV: "development"
    },
    env_production: {
      NODE_ENV: "production"
    },
  }]
};

Packages that i have used:

"@socket.io/redis-adapter": "^7.0.1",
"nodemon": "^2.0.15",
"redis": "^4.0.0",
"socket.io": "^4.4.0" 
"socket.io-client": "^4.4.0" // this package used at client side

NodeJs version: v12.16.1  
NPM version: v6.13.4  
PM2 version: v5.1.2

I am following documentation (socket.io) from here

I don't know, what that error means, but i have spent significant amount of time try many things, but havent found any thing useful.


Solution

  • I think that's because the redis@4 clients must be manually connected first:

     Promise.all([pubClient.connect(), subClient.connect()]).then(() => {
       io.adapter(createAdapter(pubClient, subClient));
       io.listen(3000);
     });
    

    @socket.io/redis-adapter README