node.jssocketssocket.ioscalabilitysticky-session

HTML5 canvas with Node.js, clustering and Socket.IO


I am creating a Node.js application for collaborative drawing on an HTML5 canvas. I am using Socket.IO to communicate and I have implemented clustering so I am able to scale my application. My lecturer told me that using clustering is a good idea, but it would not be smart to make every core in the cpu do the same thing, i.e. that defeats the purpose. So in my case it would not be smart to have 8 cores working on the exact same painting, but instead maybe have 8 different paintings, one painting for each core. I also know that Socket.IO only communicates through one core. Right now I am a bit confused on where and how to start. I know that there is this "sticky" socket.io module, but that would just share communication but not make a different painting for each core?

Here is the server I have made:

let http = require('http').Server(application);
let socketIO = require('socket.io')(http);
let cluster = require('cluster'); 
let cores= require('os').cpus().length;

if (cluster.isMaster) {
    for (let i = 0; i < cores; i++) {
        cluster.fork();
    }

} else {
    process.exit();
}

function connect(socket){
    socket.on('test',
        function emit(data) {
            socket.broadcast.emit('test', data);
        });
}

socketIO.on('connection', connect);

http.listen(port);

Solution

  • I think your CPU cores will be doing something very similar anyways, but I believe this question should be re-worded in a way that it's exploring how would you solve this through utilizing multiple CPUs assuming you have >= X paintings where X is the # of CPUs. You don't directly assign CPU's to individual paintings, OS is well optimized to come up with smart ways of choosing the best available one.

    See how the workers are setup to listen to socket connections? You can emit the data that you want to emit in each of the workers.

    Code below is taken from this SO post, I've slightly changed it.

    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 }));
    
      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) {
        // grandeFasola - emit what you what to emit here.
        socket.emit('data', 'connected to worker: ' + cluster.worker.id);
      });
    
      app.listen(80);
    }