Giter VIP home page Giter VIP logo

node-cluster-socket.io's Introduction

node-cluster-socket.io

Socket.IO doesn't work out of the box with a node.js cluster. This is a writeup based on sticky-session and the Socket.IO multiple node documentation that explains how to make them play nice if you don't feel like wrapping your server code with it.

This is a brief explanation of how this is done with heavily commented code that will make it easy for you to integrate with your project.

I use Node.js + Express.js + Socket.IO + cluster intentionally to show how it works with all those pieces.

Assumptions:

What we need to do

  • Proxy connections from the master to the workers, making sure that connections originating from the same IP address end up in the same worker
  • Persistent storage (redis instead of memory)

How does it work

Say your server runs on port 3000:

var express = require('express'),
    cluster = require('cluster'),
    sio = require('socket.io');

var port = 3000,
    num_processes = require('os').cpus().length;

if (cluster.isMaster) {
	for (var i = 0; i < num_processes; i++) {
		cluster.fork();
	}
} else {
	var app = new express();

	// Here you might use middleware, attach routes, etc.

	var server = app.listen(port),
	    io = sio(server);

	// Here you might use Socket.IO middleware for authorization etc.
}

Instead of starting the node.js server on that port and listening in each worker, we need to introduce a tiny proxy layer to make sure that connections from the same host end up in the same worker.

The way to do this is to create a single server listening on port 3000 and consistently map source IP addresses to our workers. We then pass the connection to the worker, which emits a connection event on its server. To prevent data loss where data is sent on the connection before it has been passed to the worker, the server sets the pauseOnConnect option. That way, connections are paused immediately and workers can .resume() them to receive data when they're ready. Processing then proceeds as normal:

var express = require('express'),
    cluster = require('cluster'),
    net = require('net'),
    sio = require('socket.io'),
    sio_redis = require('socket.io-redis'),
    farmhash = require('farmhash');

var port = 3000,
    num_processes = require('os').cpus().length;

if (cluster.isMaster) {
	// This stores our workers. We need to keep them to be able to reference
	// them based on source IP address. It's also useful for auto-restart,
	// for example.
	var workers = [];

	// Helper function for spawning worker at index 'i'.
	var spawn = function(i) {
		workers[i] = cluster.fork();

		// Optional: Restart worker on exit
		workers[i].on('exit', function(code, signal) {
			console.log('respawning worker', i);
			spawn(i);
		});
    };

    // Spawn workers.
	for (var i = 0; i < num_processes; i++) {
		spawn(i);
	}

	// Helper function for getting a worker index based on IP address.
	// This is a hot path so it should be really fast. The way it works
	// is by converting the IP address to a number by removing non numeric
  // characters, then compressing it to the number of slots we have.
	//
	// Compared against "real" hashing (from the sticky-session code) and
	// "real" IP number conversion, this function is on par in terms of
	// worker index distribution only much faster.
	var worker_index = function(ip, len) {
		return farmhash.fingerprint32(ip) % len; // Farmhash is the fastest and works with IPv6, too
	};

	// Create the outside facing server listening on our port.
	var server = net.createServer({ pauseOnConnect: true }, function(connection) {
		// We received a connection and need to pass it to the appropriate
		// worker. Get the worker for this connection's source IP and pass
		// it the connection.
		var worker = workers[worker_index(connection.remoteAddress, num_processes)];
		worker.send('sticky-session:connection', connection);
	}).listen(port);
} else {
    // Note we don't use a port here because the master listens on it for us.
	var app = new express();

	// Here you might use middleware, attach routes, etc.

	// Don't expose our internal server to the outside.
	var server = app.listen(0, 'localhost'),
	    io = sio(server);

	// Tell Socket.IO to use the redis adapter. By default, the redis
	// server is assumed to be on localhost:6379. You don't have to
	// specify them explicitly unless you want to change them.
	io.adapter(sio_redis({ host: 'localhost', port: 6379 }));

	// Here you might use Socket.IO middleware for authorization etc.

	// Listen to messages sent from the master. Ignore everything else.
	process.on('message', function(message, connection) {
		if (message !== 'sticky-session:connection') {
			return;
		}

		// Emulate a connection event on the server by emitting the
		// event with the connection the master sent us.
		server.emit('connection', connection);

		connection.resume();
	});
}

That should do it. Please let me know if this doesn't work or if you have any comments.

Benchmarks

There's a script you can run to test the various hashing functions. It generates a million random IP addresses and then hashes them using each of four hashing algorithms to get a consistent IP address -> array index mapping.

The time it took is printed in milliseconds (less is better) and distribution of IP addresses to array index is printed (more equal distribution the better).

To run:

$ node benchmark <num_workers>

Here's output from my machine:

$ node benchmark 4
IPv4
----------
benchmarking int31...
  time (ms): 874
  scatter: { '0': 249145, '1': 250189, '2': 249969, '3': 250697 }
benchmarking numeric_real...
  time (ms): 441
  scatter: { '0': 249084, '1': 251221, '2': 250609, '3': 249086 }
benchmarking simple_regex...
  time (ms): 281
  scatter: { '0': 247994, '1': 249241, '2': 251699, '3': 251066 }
benchmarking simple_loop...
  time (ms): 559
  scatter: { '0': 247994, '1': 249241, '2': 251699, '3': 251066 }
benchmarking farmhash...
  time (ms): 234
  scatter: { '0': 249192, '1': 250640, '2': 250570, '3': 249598 }


IPv6
----------
benchmarking int31...
  time (ms): 418
  scatter: { '0': 543029, '1': 143286, '2': 141239, '3': 172446 }
benchmarking numeric_real...
  time (ms): 821
  scatter: { NaN: 1000000 }
benchmarking simple_regex...
  time (ms): 714
  scatter: { NaN: 1000000 }
benchmarking simple_loop...
  time (ms): 1261
  scatter: { '0': 890953, '1': 34728, '2': 38949, '3': 35370 }
benchmarking farmhash...
  time (ms): 314
  scatter: { '0': 249034, '1': 250866, '2': 249923, '3': 250177 }

$

The algorithm used in the example above is "simple_loop."

node-cluster-socket.io's People

Contributors

basickarl avatar carasel avatar elad avatar harrisonkeeling avatar mickl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

node-cluster-socket.io's Issues

What's the role of radis in the cluster?

Can I just use a map in master of the cluster for the key value pair instead? If so, can you please share some code for such? Especially on the adapter part, I didn't find much document on the adapter.

Thank you in advance!

Even though this solution creates many workers, the performance has dropped down that it seems that only one worker is working.

Hello All:

I have an NodeJS-Express-Mongo web application. I improved the performance using NodeJS Cluster module. Transactions that were taking 30 seconds to run reduced their execution time to 8-10 seconds just by adding the Cluster module. It was amazing! But, sockets stopped working properly.

The overcome this sockets issue, I implemented this "node-cluster-socket.io" solution in my application. And everything looks like is running fine. When I start the app, 8 workers are created, sockets work nicely again, but the performance has dropped again, transactions are taking 30 secs. to run.

If I reduce the number of workers to 4,2 or even 1 worker the transactions always take 30 seconds to be completed. My guess is that only one worker is working even though cluster module is activated.

When I go back to my previous configuration and remove the socket fix, the performance improves again but sockets stop working properly.

Does anybody know what could be missing? (I basically copied and pasted the configuration in the readme file)

CORS issue on cluster with nginx load balancer

I have CORS issue when use this cluster server behind nginx load balancer.
how can I modify its origin so that CORS issue not showing?
I send Auhorization header.

it is said:

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
Please help. Thanks in advance

simple put socket on your sample

Hi i'm implementing simple socket by your code, but from android i cant check login

my login function and socket work fine without using Cluster, could you help me?

var express   = require('express'),
    cluster   = require('cluster'),
    net       = require('net'),
    sio       = require('socket.io'),
    mysql     = require('mysql'),
    sio_redis = require('socket.io-redis');

var port          = 3000,
    num_processes = require('os').cpus().length;

var connection = mysql.createConnection(
    {
        host              : 'localhost',
        user              : 'root',
        password          : 'a',
        database          : 'shoot',
        multipleStatements: true
    });

if (cluster.isMaster) {
    var workers = [];

    var spawn = function (i) {
        workers[i] = cluster.fork();

        workers[i].on('exit', function (worker, code, signal) {
            console.log('respawning worker', i);
            spawn(i);
        });
    };

    // Spawn workers.
    for (var i = 0; i < num_processes; i++) {
        spawn(i);
    }
    var worker_index = function (ip, len) {
        var s = '';
        for (var i = 0, _len = ip.length; i < _len; i++) {
            if (!isNaN(ip[i])) {
                s += ip[i];
            }
        }

        return Number(s) % len;
    };

    var server = net.createServer({pauseOnConnect: true}, function (connection) {
        var worker = workers[worker_index(connection.remoteAddress, num_processes)];
        worker.send('sticky-session:connection', connection);
    }).listen(port);
} else {
    // Note we don't use a port here because the master listens on it for us.
    var app = new express();

    // Here you might use middleware, attach routes, etc.

    // Don't expose our internal server to the outside.
    var server = app.listen(0, 'localhost'),
        io     = sio(server);

    io.adapter(sio_redis({host: 'localhost', port: 6379}));
    process.on('message', function (message, connection) {
        if (message !== 'sticky-session:connection') {
            return;
        }

        server.emit('connection', connection);

        connection.resume();
    });

    io.on('login', function (data) {
        login(data.username, data.password, function (success, value) {
            if (success)
                redisClient.set(data.username, socket.id);
            socket.emit('login', {result: success, id: value});
        });
    });

}

function login(username, password, callback) {
    var query = "SELECT * FROM users WHERE username = ?";
    connection.query(query, [username], function (err, results) {
        if (err) return callback(false);
        if (results.length === 0) return callback(false);
        var user = results[0];

        if (!bcrypt.compareSync(password, user.password)) {
            return callback(false);
        }

        callback(true, {
            id: user.id.toString(),
        });
    });
}

Issue with IPV6 addresses preceding IPV4 on Linux

Not sure if this affects other systems but I noticed an issue when trying this out on Fedora 23.

connection.remoteAddress from net.createServer includes what appears to be an IPV6 address preceding the IPV4 address. Example: ::ffff:127.0.0.1

This can be remedied by simply using a better conditional inside worker_index

if (!isNaN(ip[i])) { s += ip[i]; }
I have the edit for the readme.md available in a fork.

I have a problem with 1 brower and 3workers

1 . I have http and ws request
2 . use cluster, 1master and 3workers
3 . if i use redis like demo, 1 browser (1 ip) all the request will be handled by one worker,and other workers will do nothing
4. I want to rebalence the http request to the 3 workers , and ws handle by the right worker

Use farmhash in the example & benchmarks

Google's farmhash (Node.js port) is an extremely fast hash with good quality, especially given that IPs don't tend to have an even distribution (private ranges, multicast, geographical allocation biases).

Running the benchmark gives good results and is faster than even a simple loop (due to the fact that FarmHash is written in C++):

~/Desktop/node-cluster-socket.io(master ✗) node benchmark 4
benchmarking int31...
  time (ms): 1057
  scatter: { '0': 249393, '1': 250495, '2': 250467, '3': 249645 }
benchmarking numeric_real...
  time (ms): 566
  scatter: { '0': 248780, '1': 250423, '2': 251212, '3': 249585 }
benchmarking simple_regex...
  time (ms): 307
  scatter: { '0': 247841, '1': 248563, '2': 252151, '3': 251445 }
benchmarking farmhash32...
  time (ms): 268
  scatter: { '0': 249802, '1': 249522, '2': 249922, '3': 250754 }
benchmarking simple_loop...
  time (ms): 532
  scatter: { '0': 247841, '1': 248563, '2': 252151, '3': 251445 }

Of course, this is nitpicking, but useful if you are dealing with high levels of traffic where Node's cluster package is most useful.

Provided this is OK, I can submit a PR w/ results + some prose to explain.

HTTPS

Very informative but how would I modify this to support HTTPS?

Confused on how to run this in multiple servers

elad, this is a great info and code that i can use for my scaling. Apologies if it is a dumb ask, as i am new into nodejs, socket.io, my confusion and issue is how can i run this in multiple servers in amazon. would it be i load balance to the servers and then run my app (nodejs/socketio) code in each server and each server will have its workers (since they will have more than one cpu)?

would be great if you can help me understand that on how to set it up

Can I use Master, and Workers process communication when also using Sticky Session?

I'm trying to use sticky-session for socket.io as a load balancer.

I use this example from the main page:

    var express = require('express'),
        cluster = require('cluster'),
        net = require('net'),
        sio = require('socket.io'),
        sio_redis = require('socket.io-redis'),
        farmhash = require('farmhash');
    
    var port = 3000,
        num_processes = require('os').cpus().length;
    
    if (cluster.isMaster) {
    	// This stores our workers. We need to keep them to be able to reference
    	// them based on source IP address. It's also useful for auto-restart,
    	// for example.
    	var workers = [];
    
    	// Helper function for spawning worker at index 'i'.
    	var spawn = function(i) {
    		workers[i] = cluster.fork();
    
    		// Optional: Restart worker on exit
    		workers[i].on('exit', function(code, signal) {
    			console.log('respawning worker', i);
    			spawn(i);
    		});
        };
    
        // Spawn workers.
    	for (var i = 0; i < num_processes; i++) {
    		spawn(i);
    	}
    
    	// Helper function for getting a worker index based on IP address.
    	// This is a hot path so it should be really fast. The way it works
    	// is by converting the IP address to a number by removing non numeric
      // characters, then compressing it to the number of slots we have.
    	//
    	// Compared against "real" hashing (from the sticky-session code) and
    	// "real" IP number conversion, this function is on par in terms of
    	// worker index distribution only much faster.
    	var worker_index = function(ip, len) {
    		return farmhash.fingerprint32(ip) % len; // Farmhash is the fastest and works with IPv6, too
    	};
    
    	// Create the outside facing server listening on our port.
    	var server = net.createServer({ pauseOnConnect: true }, function(connection) {
    		// We received a connection and need to pass it to the appropriate
    		// worker. Get the worker for this connection's source IP and pass
    		// it the connection.
    		var worker = workers[worker_index(connection.remoteAddress, num_processes)];
    		worker.send('sticky-session:connection', connection);
    	}).listen(port);
    } else {
        // Note we don't use a port here because the master listens on it for us.
    	var app = new express();
    
    	// Here you might use middleware, attach routes, etc.
    
    	// Don't expose our internal server to the outside.
    	var server = app.listen(0, 'localhost'),
    	    io = sio(server);
    
    	// Tell Socket.IO to use the redis adapter. By default, the redis
    	// server is assumed to be on localhost:6379. You don't have to
    	// specify them explicitly unless you want to change them.
    	io.adapter(sio_redis({ host: 'localhost', port: 6379 }));
    
    	// Here you might use Socket.IO middleware for authorization etc.
    
    	// Listen to messages sent from the master. Ignore everything else.
    	process.on('message', function(message, connection) {
    		if (message !== 'sticky-session:connection') {
    			return;
    		}
    
    		// Emulate a connection event on the server by emitting the
    		// event with the connection the master sent us.
    		server.emit('connection', connection);
    
    		connection.resume();
    	});
    }

My problem are these lines:

	// Listen to messages sent from the master. Ignore everything else.
	process.on('message', function(message, connection) {
		if (message !== 'sticky-session:connection') {
			return;
		}

		// Emulate a connection event on the server by emitting the
		// event with the connection the master sent us.
		server.emit('connection', connection);

		connection.resume();
	});

For my app, I need to use "process.on / process.send" communication, as well as "worker[worker_id].send" when in my Master cluster.

Is it banned to do so with sticky session? Can it break the communication?

I want to use something like that:

	process.on('message', function(message, connection) {
		if (message !== 'sticky-session:connection')
		{
			if(message.action == "calculX")
			{
				//redirect to function in this worker
			}	
			if(message.action == "calculY")
			{
				//redirect to function in this worker
			}				
			
			return;
		}

		// Emulate a connection event on the server by emitting the
		// event with the connection the master sent us.
		server.emit('connection', connection);

		connection.resume();
	});

Is it allowed or will it break the "connection" system?

Thanks in advance.

How to use the example mentioned in node-cluster-socket.io to work with domains ?

Hi Elad,

I'm struggling with a problem where I'm using using clusters along with domains. Due to this, my chat functionality has stopped working. The reason for this is handshaking is not working as it was in a single threaded environment. I'm planning to modify my existing code using your code but facing few issues:

  • You are accepting all the connections on master, won't this bottleneck the application in case the number of connections increases.
  • How can we use domains in your sample code ?
  • I'm using one more server which is listening on 8080 port(http) to rediect it to https. Should I create this server in my master process ?

Here is my code which I'm using right now:


'use strict';

// Set default node environment to development
process.env.NODE_ENV = process.env.NODE_ENV || 'development';

var express = require('express');
var mongoose = require('mongoose');
var config = require('./config/environment');
var session = require('express-session');
var FB = require('fb');
var redisStore = require('connect-redis')(session);
var crypto = require('crypto');
var fs = require('fs');
var cluster = require('cluster');
var domain = require('domain');

var socketIo = require('./config/socketio');

var REDIS = require('redis')

// redis clients
var store = REDIS.createClient();
//var pub = REDIS.createClient();
//var sub = REDIS.createClient();

var pub = REDIS.createClient(config.redis.port, config.redis.host, {return_buffers: true});
var sub = REDIS.createClient(config.redis.port, config.redis.host, {return_buffers: true});


function uuidFromBytes(rnd) {
  rnd[6] = (rnd[6] & 0x0f) | 0x40;
  rnd[8] = (rnd[8] & 0x3f) | 0x80;
  rnd = rnd.toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/);
  rnd.shift();
  return rnd.join('-');
}

if(cluster.isMaster) {
    var numWorkers = require('os').cpus().length;

    console.log('Master cluster setting up ' + numWorkers + ' workers...');

    for(var i = 0; i < numWorkers; i++) {
        cluster.fork();
    }

    cluster.on('online', function(worker) {
        console.log('Worker ' + worker.process.pid + ' is online');
    });


    cluster.on('exit', function(worker, code, signal) {
        console.log('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
        console.log('Starting a new worker');
        cluster.fork();
    });

} else {
    var d = domain.create ();
    d.on ("error", function (error){
      //something unexpected occurred
      console.error('uncaught error', error.stack);
      var killtimer = setTimeout(function()
      {
         process.exit(1);
      }, 30000);
      // But don't keep the process open just for that!
      killtimer.unref();
      //stop taking new requests.
      server.close();
      //Let the master know we're dead.  This will trigger a
      //'exit' in the cluster master, and then it will fork
      //a new worker.
      cluster.worker.exit();
    });

    // Connect to database
    mongoose.connect(config.mongo.uri, config.mongo.options);

    // Populate DB with sample data
    if(config.seedDB) { require('./config/seed'); }

    // Setup server
    var app = express();
    var server = require(config.protocol);

    if(config.env === 'production') {

      d.run(function(){
        server = server.createServer(config.sslOptions, app);
      });

      // Redirect from http port 80 to https
      var http = require('http');
      http.createServer(function (req, res) {
          res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
          res.end();
      }).listen(8080);
    } else{

      d.run (function (){
        server = server.createServer(app);
      });

    }

    app.use(session({
      genid: function(req) {
        return uuidFromBytes(crypto.randomBytes(16)) // use UUIDs for session IDs
      },
      secret: config.sessionSecret,
      resave: false,
      store: new redisStore({ host: config.redis.host, port: config.redis.port}),
      saveUninitialized: true
    }));


    require('./config/express')(app);
    require('./config/redis')();
    require('./routes')(app);


    if(!config.facebook.appId || !config.facebook.appSecret) {
        throw new Error('facebook appId and appSecret required in config.js');
    }


    // Start server
    server.listen(config.port, config.ip, function () {
      console.log('Express server listening on %d, in %s mode', config.port, app.get('env'));
    });

    var socketio_redis = require('socket.io-redis');

    var socketIO = require('socket.io')(server, {
        serveClient: (config.env === 'production') ? false : true,
        path: '/socket.io-client'
    });

    sub.subscribe('chat');

    socketIO.adapter(socketio_redis( {host: 'localhost', port: 6379}));

    socketIo.createSocketConnection(socketIO, sub, pub, store);

    // Expose app original
    exports = module.exports = app;
}

How can I modify the above code to make it work for chat application ?

Thanks

worker_index doesn't work for ipv6

With an ip address of ::ffff:127.0.0.1 worker_index returns NaN

I changed it to :

var worker_index = function(ip, len) {
    var s = '';
    for (var i = 0, _len = ip.length; i < _len; i++) {
         if (parseInt(ip[i], 10)) {
              s += ip[i];
         }
     }

     return Number(s) % len || 0;
};

Not sure how bad that effects performance.

Make proxy usage available

Right now running this cluster behind a proxy always uses ip 127.0.0.1. I did quite some research, but I couldn't find if there is a way on how to access the X-Real-IP or X-Forwarded-For header. Any ideas on how to solve that?

Using Express.js instead of Net

Is it possible to use Express.js instead of Net?

I guess Express would be the default usecase for most users. Currently i do have 2 servers: Net for Socket.io (like mentioned here) and Express.js for http requests. Now i have the problem to activate SSL and Cors on Net while it was pretty easy on Express.js.

How to send messages between workers?

Hi,
followed your example step by step and seems to work.
But IMHO this appears to solve one half of the issue.
Client side, i have a socket connecting to a namespace per object.
When first user joins the associated room, server tries to find if a room already exists. If it doesn't, a new room is created :

// creating and initializing a room
app.get("socketio").of(roomName).on("connection", function(socket){
    // implement the socket behaviour
})

A new user might come in and join the room.
As per your code, it might be redirected to another worker ...
Then, when testing if the room already exists (on this worker), we might recreate the room again :

// testing if a room exists
 if (_.keys(app.get("socketio").nsps).indexOf(roomName) == -1) {
   // recreate the room :(
}

So my questions are :
1- Do you have any idea how we could adapt your code so that we have a way to test room existence globally across workers ? Is it supported by the adapter (which doc is very succint to say the least)
2-How then to redirect the messagereceived from a worker to the true object owner ? Is it supported also by redis adapter ?

Nevertheless, your explanation was very helpful and its almost the unique resource I could find about scaling socket.io.

Why resume()?

I've been trying sticky-session with a 4-process cluster and it seems to be working.

However, on your example you mention explicitly the need to call resume() for a slave to take over incoming socket data. The thing is, sticky-session doesn't use resume() and it seems to be working nonetheless.

¿Could you please shed some light? I'm really new to this.

Production Use

Thank you the research.

I see a small typo bug.
It should be
var worker = workers[worker_index(connection.remoteAddress, num_processes)];

The scripts works well for me.

Duplicated messages

I'm looking for a way to handle the duplicated messages I got when I emit a message with this code, any idea?

Sticky sessions using client's custom data

Hello @elad

Thank you for your great example of using socket.io and cluster. In front of me there was a similar problem, and your example is very helpful to understand.

I was a little modified your example, I would like to make the session became attached to specified worker process is not over IP, but for client's custom data. For example user ID.

example

I started working with Node.js not so long ago, could you leave a few comments about my solution.

Kind regards.

It is not working for me

I am using your solution, but when I tried to run in browser, it is just hanging, and there is no response at all. My versions are:
Node: 0.10.x
Express: 4.2.0
redis: 0.10.1
socket.io: 1.0.6
socket.io-redis: 0.1.3

It is exactly same as yours
I think this is confusion:
process.on('message', function(message, connection) {
if (message !== 'sticky-session:connection') {
return;
}
server.emit('connection', connection);
});
How to make the real code to process the request, if you are using app.listen(0, 'localhost')

My code is as follows:

var cluster = require('cluster'),
net = require('net');

var num_processes = process.env.WORKERS || require('os').cpus().length;

if (cluster.isMaster) {
console.log('start cluster with %s workers', num_processes);
var workers = [];
var spawn = function(i) {
workers[i] = cluster.fork();

  workers[i].on('exit', function(worker, code, signal) {
      console.log('respawning worker', i);
      spawn(i);
  });

};
for (var i = 0; i < num_processes; i++) {
spawn(i);
}
var worker_index = function(ip, len) {
var s = '';
for (var i = 0, _len = ip.length; i < _len; i++) {
if (ip[i] !== '.') {
s += ip[i];
}
}
return Number(s) % len;
};
var server = net.createServer(function(connection) {
var worker = workers[worker_index(connection.remoteAddress, num_processes)];
worker.send('sticky-session:connection', connection);
}).listen(3000);
} else {

var init = require('./config/init')(),
config = require('./config/config'),
mongoose = require('mongoose');
var sio = require('socket.io');
var sio_redis = require('socket.io-redis');

//Bootstrap db connection
var db = mongoose.connect(config.db);
//Init the express application
var app = require('./config/express')(db);

//Start the app by listening on <port>
var server = app.listen(0, 'localhost');

var io = sio(server);
io.adapter(sio_redis({ host: 'localhost', port: 6379 }));

var socketService = require('./app/services/sockets');
socketService()['init'](io);


// Listen to messages sent from the master. Ignore everything else.
process.on('message', function(message, connection) {
     if (message !== 'sticky-session:connection') {
        return;
    }

    server.emit('connection', connection);
});

//Logging initialization
console.log('MEAN.JS application started on port ' + config.port);

}

process.on('uncaughtException', function (err) {
console.error((new Date).toUTCString() + ' uncaughtException:', err.message)
console.error(err.stack)
process.exit(1)
})

Correct Approach?

Seems to me that the approach proposed in this repo is not the correct one, and I am almost inclined to define it as an anti-pattern.
My reasoning is that socket.io should be cluster and loadbalancer compatible from the group up. It is much easier to make socket.io work well with multiple servers than trying to solve this issue on top of it. For socket-io is just a matter of using a persistent, distributed store such as redis (or any database for that matter), to store the connection/session data, so that the communication can be established even if the client hits several servers during the lifetime of a socket.io session.
All these other solutions trying to bend the natural behaviours of the cluster module and loadbalancers are just going to bring pain and despair to people...

connection.remoteAddress is undefined

Hi everybody,

I'm using nginx as a reverse-proxy and connection.remoteAddress is undefined.

var server = net.createServer(function(connection) {
...
connection.remoteAddress //undefined
...
}).listen('/tmp/node.sock');

Do you have any ideas how I could get the IP ?

When i'm testing without nginx everything is fine.

How to resolve mutiple message coming when client send a mesage?

I put some code to your example as below bold font marked:
.................
process.on('message', function(message, connection) {
if (message !== 'sticky-session:connection') {
return;
}
io.sockets.on('connection', function(socket) {
socket.on('set username ' + socketId, function(data, callback) {
console.log('set', 1);
});
});

server.emit('connection', connection);
connection.resume();
});
...............
But console.log('set', 1); print mutiple times every message coming, How to avoid this problem?

Why sticky-session ?

Hey @elad,

I have a question related to the great example you provided here: Can you explain why we even need a sticky session? I don't understand why having the redis adapter isn't enough to support multi processes?
I mean, I understand that the sticky session comes to make sure a request from a specific IP goes to a specific worker but isn't the redis adapter a solution that's supposed to make sure all socket.io processes know about all connections and can handle them all asynchronously?

how to get original client ip while using Haproxy

Hi elad,
I'm using haproxy as a reverse-proxy, I'm getting haproxy ip as client ip at 'connection.remoteAddress'.In my use case clients will connect via haproxy and using node-cluster-socket.io for sticky-session. If i use haproxy x-forwarded-host now how do i get original client ip from node-cluster-socket.io

IPv6 support

None of the hash functions are working correctly with IPv6. You should definitely indicate that in your readme. Also you may replace the hash function with farmhash32:

IPv6
----------
benchmarking int31...
  time (ms): 417
  scatter: { '0': 568888, '1': 220419, '2': 94915, '3': 115778 }
benchmarking numeric_real...
  time (ms): 826
  scatter: { NaN: 1000000 }
benchmarking simple_regex...
  time (ms): 704
  scatter: { NaN: 1000000 }
benchmarking simple_loop...
  time (ms): 1292
  scatter: { '0': 891094, '1': 35005, '2': 38494, '3': 35407 }
benchmarking farmhash...
  time (ms): 316
  scatter: { '0': 250239, '1': 249119, '2': 250724, '3': 249918 }

HTTPS Support

This works great.
Could you please suggest if (and how) this can work for HTTPS server?

Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.