Nodejs is asynchronous. But single threaded. When a synchronous workload is executed, the event loop is blocked.
Can we make node multi threaded to increase performance?
You can look into cluster mode in recent versions of Node.js.
Quoting the example from the above page for reference:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers.
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
// Workers can share any TCP connection
// In this case it is an HTTP server
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
This code starts a number of workers, and you see a clear separation between master and worker code.
These processes can communicate by sending messages.
I understand that I can use Nodes cluster module in order to create several workers all serving the same socket connection (example from docs):
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case its a HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
However, what if I instead of serving the same connection want each worker to run their own server, each listening on a separate port?
You can pass environment variables to each child, allowing the master process to assign them ports:
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
var pidToPort = {};
var worker, port;
for (var i = 0; i < numCPUs; i++) {
port = 8000 + i;
worker = cluster.fork({port: port});
pidToPort[worker.process.pid] = port;
}
console.log(pidToPort);
cluster.on('exit', function(worker, code, signal) {
// Use `worker.process.pid` and `pidToPort` to spin up a new worker with
// the port that's now missing. If you do so, don't forget to delete the
// old `pidToPort` mapping and add the new one.
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Start listening on `process.env.port` - but first, remember that it has
// been cast to a string, so you'll need to parse it.
console.log(process.env.port);
}
I'm running a test trying to draw maximum delivery speed from a Node HTTP server. It's a simple server.
In my test I have 50K virtual clients establishing a permanent connection with the server (I run ulimit -n 99999 before). Then, upon another event, an HTTP connection to a different port, the server sends one message to each virtual client. At the end all clients receive the corresponding message.
Sending all messages takes minutes in my tests. Are there any recommendations that would help me improve these measurements so that I can send 50K messages in seconds instead of minutes?
The server is running in a m1.medium AWS instance. The idea is to improve performance with the same platform.
Copying the server code:
var http = require("http");
var connectionResponse = [];
var connectionIndex = 0;
http.createServer(function(request, response) {
console.log("Received connection " + connectionIndex);
response.setTimeout(1200000, function() {
console.log("Socket timeout");
});
connectionResponse[connectionIndex] = response;
connectionIndex++;
}).listen(8888);
http.createServer(function(request, response) {
console.log("8887 connected - Will respond");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Triggered all responses");
response.end();
console.log("Begin notifications:" + new Date().toISOString());
for(var i = 0; i < connectionIndex; i++) {
connectionResponse[i].writeHead(200, {"Content-Type": "text/plain", "Content-Length": 4, "transfer-encoding" : ""});
connectionResponse[i].write("CAFE");
connectionResponse[i].end();
}
console.log("End notifications" + new Date().toISOString());
}).listen(8887);
Setting this http://nodejs.org/api/http.html#http_agent_maxsockets to a sufficient number
var http = require('http');
http.globalAgent.maxSockets = xxx;
var https = require('https');
https.globalAgent.maxSockets = xxx;
Using nodejs clustering module, http://nodejs.org/api/cluster.html
Now, regarding the clustering, it really depends on what you want to do. The default example can go long way before you have to tweak it. An example would be
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
// Fork workers.
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
// Workers can share any TCP connection
// In this case its a HTTP server
http.createServer(function(req, res) {
res.writeHead(200);
res.end("hello world\n");
}).listen(8000);
}
I have created the code like for chat application
The above code is working fine for single server but for cluster server it gives error
client not handshaken client should reconnect, socket.io in cluster
can you please anyone help me to workout for cluster server
Thanks.
Edit: Code
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
var sio = require('socket.io');
var server;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
} cluster.on('online', function(worker) {
console.log('A worker with #' + worker.id);
});
cluster.on('listening', function(worker, address) {
console.log('A worker is now connected to ' + address.address + ':' + address.port);
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
server = http.createServer(function(req, res) {
res.writeHead(200); res.end('hello world\n');
}).listen(8000);
_socketServer();
}
var _socketServer = function() {
io = sio.listen(server);
io.set("log level", 1);
io.sockets.on("connection", function (socket) { });
});
Error: client not handshaken client should reconnect, socket.io in cluster
Try using redis server in cluster and subscribe to events exposed by redis server. It is one of the solution to solve this problem.
Edit:
Some code sample/architecture/frameworks used and all other information would be very helpful
Code:
Hi Guys,
Actually i am trying to create a chat application using socket.io
For single server it is working fine but when i try creating using cluster server then its
not working for me
Throws error :
client not handshaken client should reconnect, socket.io in cluster
web socket invalid
These error are continuously arising in console
Can someone help me to proceed for cluster server for socket.IO
var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;
var sio = require('socket.io');
var server;
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('online', function(worker) {
console.log('A worker with #' + worker.id);
});
cluster.on('listening', function(worker, address) {
console.log('A worker is now connected to ' + address.address + ':' + address.port);
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
}
else {
server = http.createServer(function(req, res) {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
_socketServer();
}
var _socketServer = function() {
io = sio.listen(server);
io.set("log level", 1);
io.sockets.on("connection", function (socket) {
});
});
I am getting error like this
client not handshaken client should reconnect, socket.io in cluster
You can use this way to store socket information for all workers:
var ClusterStore = require('strong-cluster-socket.io-store')(io);
io = require("socket.io").listen(server)
io.set({'store': new ClusterStore()});
Use npm to install module 'strong-cluster-socket.io-store'
I have a big problem sice 1 week. I try to convert my node.JS project actually run on single core to multi core with cluster.
With websockets, at this moment, i have no problems for events but, for xhr-polling or jsonp-polling, i have big problems with socket.io on cluster mode.
this is my server configuration :
00-generic.js
'use strict';
var http = require('http'),
os = require('os'),
cluster = require('cluster');
module.exports = function(done) {
var app = this.express,
port = process.env.PORT || 3000,
address = '0.0.0.0';
if(this.env == 'test'){
port = 3030;
}
var self = this;
var size = os.cpus().length;
if (cluster.isMaster) {
console.info('Creating HTTP server cluster with %d workers', size);
for (var i = 0; i < size; ++i) {
console.log('spawning worker process %d', (i + 1));
cluster.fork();
}
cluster.on('fork', function(worker) {
console.log('worker %s spawned', worker.id);
});
cluster.on('online', function(worker) {
console.log('worker %s online', worker.id);
});
cluster.on('listening', function(worker, addr) {
console.log('worker %s listening on %s:%d', worker.id, addr.address, addr.port);
});
cluster.on('disconnect', function(worker) {
console.log('worker %s disconnected', worker.id);
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker %s died (%s)', worker.id, signal || code);
if (!worker.suicide) {
console.log('restarting worker');
cluster.fork();
}
});
} else {
http.createServer(app).listen(port, address, function() {
var addr = this.address();
console.log('listening on %s:%d', addr.address, addr.port);
self.server = this;
done();
});
}
};
03-socket.io.js
"use strict";
var _ = require('underscore'),
socketio = require('socket.io'),
locomotive = require('locomotive'),
RedisStore = require("socket.io/lib/stores/redis"),
redis = require("socket.io/node_modules/redis"),
v1 = require(__dirname + '/../app/socket.io/v1'),
sockets = require(__dirname + '/../../app/socket/socket'),
config = require(__dirname + '/../app/global'),
cluster = require('cluster');
module.exports = function () {
if (!cluster.isMaster) {
this.io = socketio.listen(this.server);
var pub = redis.createClient(),
sub = redis.createClient(),
client = redis.createClient();
this.io.enable('browser client minification'); // send minified client
this.io.enable('browser client etag'); // apply etag caching logic based on version number
this.io.enable('browser client gzip'); // gzip the file
this.io.set("store", new RedisStore({
redisPub : pub,
redisSub : sub,
redisClient : client
}));
this.io.set('log level', 2);
this.io.set('transports', [
'websocket',
'jsonp-polling'
]);
this.io.set('close timeout', 24*60*60);
this.io.set('heartbeat timeout', 24*60*60);
this.io.sockets.on('connection', function (socket) {
console.log('connected with ' + this.io.transports[socket.id].name);
// partie v1 #deprecated
v1.events(socket);
// partie v1.1 refaite
_.each(sockets['1.1'], function(Mod) {
var mod = new Mod();
mod.launch({
socket : socket,
io : this.io
});
}, this);
}.bind(this));
}
};
With polling, the client connects from time to time on a different process than that initiated listeners. Similarly, the communication server to the client with emit.
With a little searching, I found it necessary to pass by a store for socket.io to share the data connection. So I built RedisStore socket.io as shown in the documentation but even with that, I find myself with events not arriving safely and I still get this error message:
warn: client not handshaken client should reconnect
EDIT
Now, the warn error is not called. I change the redisStore to socket.io-clusterhub BUT now, events are not always called. Sometimes as if the polling request was captured by another worker than that which began the listeners and so it nothing happens. Here is the new configuration:
'use strict';
var http = require('http'),
locomotive = require('locomotive'),
os = require('os'),
cluster = require('cluster'),
config = require(__dirname + '/../app/global'),
_ = require('underscore'),
socketio = require('socket.io'),
v1 = require(__dirname + '/../app/socket.io/v1'),
sockets = require(__dirname + '/../../app/socket/socket');
module.exports = function(done) {
var app = this.express,
port = process.env.PORT || 3000,
address = '0.0.0.0';
if(this.env == 'test'){
port = 3030;
}
var self = this;
var size = os.cpus().length;
this.clusterStore = new (require('socket.io-clusterhub'));
if (cluster.isMaster) {
for (var i = 0; i < size; ++i) {
console.log('spawning worker process %d', (i + 1));
cluster.fork();
}
cluster.on('fork', function(worker) {
console.log('worker %s spawned', worker.id);
});
cluster.on('online', function(worker) {
console.log('worker %s online', worker.id);
});
cluster.on('listening', function(worker, addr) {
console.log('worker %s listening on %s:%d', worker.id, addr.address, addr.port);
});
cluster.on('disconnect', function(worker) {
console.log('worker %s disconnected', worker.id);
});
cluster.on('exit', function(worker, code, signal) {
console.log('worker %s died (%s)', worker.id, signal || code);
if (!worker.suicide) {
console.log('restarting worker');
cluster.fork();
}
});
} else {
var server = http.createServer(app);
this.io = socketio.listen(server);
this.io.configure(function() {
this.io.enable('browser client minification'); // send minified client
this.io.enable('browser client etag'); // apply etag caching logic based on version number
this.io.enable('browser client gzip'); // gzip the file
this.io.set('store', this.clusterStore);
this.io.set('log level', 2);
this.io.set('transports', [
'websocket',
'jsonp-polling'
]);
//this.io.set('close timeout', 24*60*60);
//this.io.set('heartbeat timeout', 24*60*60);
}.bind(this));
this.io.sockets.on('connection', function (socket) {
console.log('connected with ' + this.io.transports[socket.id].name);
console.log('connected to worker: ' + cluster.worker.id);
// partie v1 #deprecated
v1.events(socket);
// partie v1.1 refaite
_.each(sockets['1.1'], function(Mod) {
var mod = new Mod();
mod.launch({
socket : socket,
io : this.io
});
}, this);
}.bind(this));
server.listen(port, address, function() {
var addr = this.address();
console.log('listening on %s:%d', addr.address, addr.port);
self.server = this;
done();
});
}
};
From that source : http://socket.io/docs/using-multiple-nodes/
If you plan to distribute the load of connections among different
processes or machines, you have to make sure that requests associated
with a particular session id connect to the process that originated
them.
This is due to certain transports like XHR Polling or JSONP Polling
relying on firing several requests during the lifetime of the
“socket”.
To route connections to the same worker every time :
sticky-session
This is, in the socket.io documentation, the recommended way to route requests to the same worker every time.
https://github.com/indutny/sticky-session
A simple performant way to use socket.io with a cluster.
Socket.io is doing multiple requests to perform handshake and
establish connection with a client. With a cluster those requests may
arrive to different workers, which will break handshake protocol.
var sticky = require('sticky-sesion');
sticky(function() {
// This code will be executed only in slave workers
var http = require('http'),
io = require('socket.io');
var server = http.createServer(function(req, res) {
// ....
});
io.listen(server);
return server;
}).listen(3000, function() {
console.log('server started on 3000 port');
});
To pass messages between nodes :
socket.io-redis
This is, in socket.io documentation, the recommended way to share messages between workers.
https://github.com/automattic/socket.io-redis
By running socket.io with the socket.io-redis adapter you can run
multiple socket.io instances in different processes or servers that
can all broadcast and emit events to and from each other.
socket.io-redis is used this way :
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
Also
I think you are not using socket.io v1.0.0. You might want to update your version in order to get more stability.
You can check their migration guide at http://socket.io/docs/migrating-from-0-9/
There is a step missing from the socket.io docs when using
var io = require('socket.io')(3000);
var redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
You need to tell the client that you want to use 'websockets' as the only form of transport or it will not work... so for the constructor on the client use
io.connect(yourURL , { transports : ['websocket']});
see my answer to a similar question here ( my answer might be more appropriate on this thread ):
https://stackoverflow.com/a/30791006/4127352
The below code work for me, this is socket.io who created clusters, i set config.clusterSticky on true for activate compatibility clusters and socket.io
'use strict';
/*
var cl = console.log;
console.log = function(){
console.trace();
cl.apply(console,arguments);
};
*/
var cluster = require('cluster'),
config = require('./config/all'),
deferred = require('q').defer(),
express = require('express'),
app = express(),
http = require('http'),
sticky = require('socketio-sticky-session'),
io = require('socket.io');
// Code to run if we're in the master process or if we are not in debug mode/ running tests
if ((cluster.isMaster) &&
(process.execArgv.indexOf('--debug') < 0) &&
(process.env.NODE_ENV !== 'test') && (process.env.NODE_ENV !== 'development') &&
(process.execArgv.indexOf('--singleProcess') < 0) &&
(!config.clusterSticky)) {
console.log('for real!');
// Count the machine's CPUs
var cpuCount = process.env.CPU_COUNT || require('os').cpus().length;
// Create a worker for each CPU
for (var i = 0; i < cpuCount; i += 1) {
console.log('forking ', i);
cluster.fork();
}
// Listen for dying workers
cluster.on('exit', function (worker) {
// Replace the dead worker, we're not sentimental
console.log('Worker ' + worker.id + ' died :(');
cluster.fork();
});
// Code to run if we're in a worker process
} else {
var port = config.http.port;
var workerId = 0;
if (!cluster.isMaster) {
workerId = cluster.worker.id;
}
var server = http.createServer(app);
io.listen(server);
//TODO routes etc (core)
server.on('listening', function () {
console.log('Slave app started on port ' + port + ' (' + process.env.NODE_ENV + ') cluster.worker.id:', workerId);
});
if(config.clusterSticky && (process.env.NODE_ENV !== 'test') && (process.env.NODE_ENV !== 'development')) {
sticky(server).listen(port);
} else {
server.listen(port);
}
deferred.resolve(server);
}
module.exports = deferred.promise;