Are there any best practises or recomendations to use WS together with cluster (https://nodejs.org/api/cluster.html)?
It should just work without any problems. But there no general best practices required here yet.
Sorry, but is there somthing like RedisStore for the WS?
(please see description problem and its solution for soket.io library http://blog.rstack.cc/node_js__using_socket_io_with_cluster_module.html)
That problem does not apply to websockets as they don't use polling, which requires request tracking and therfore state.
Thank again for your quick answer! So If I use cluster module, broadcasting will work fine for all connections?
There are no broadcasting API's in this module.
Hmm..
I see in the documentation:
Server sending broadcast data
var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({ port: 8080 });
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
client.send(data);
});
};
As you can see, it's not an official API. It just iterates over the connected clients array. It's example how you could implement broadcasting. It's by no means a full broadcast API as there are many optimizations to made in a proper broadcasting API nor does it take multiple servers in consideration like full broadcast API's would. If you need full ledged broadcasting functionality you need to write code for that your self.
@3rd-Eden any tips on getting started? just the idea behind it so lets say I have 2 server
user1 - connected to server1
user2 - connected to server2
now user1 do some action and I need to trigger message to user2 how I do this across servers? what the idea of achiving this? in 1 server I just store all users connection in array
I was wondering that too. I'm using express in currently a non clustered environment and everything is fine. But if I cluster the app, this will spawn for each worker a new express server and subsequently a new websocketserver. Unless doing some sticky session, a client connected initially on worker 1, may go on another request on worker 2 and obviously the wss from the worker 2 has actually no clue of the connection initiated on the worker 1 - and it will fail.
So, in one of the example in this repository, there is something for the communication between master and its children, and I see that possible that a child send the request to the master, which in turns send that to the client (or to the child where the connection originated from and then to the client) but it feels super complicated for a quite simple problem. Anything easier?
Can you please share you code for sticky sessions?
@mato75, it's not code specific, but related to how you configure your server. On Heroku, this is how you'd do it: https://devcenter.heroku.com/articles/session-affinity
Then what is the purpose of https://github.com/indutny/sticky-session library?
a clustered implementation of websocket is much appreciated.
the sticky-session does not work on my side.
any hint ?
thanks
You don't need sticky sessions for websockets. The connection is permanent.
@lpinca but when the connection died. all the wss died too.
is there a way where if the worker died only the wss attached to that specific worker ?
If a worker dies only the connections of that specific worker will be closed.
You don't need sticky sessions for websockets. The connection is permanent.
But are they shared between clusters in NodeJS? So this is my approach:
An event occurs, maybe someone sent him a message or a notification or something that requires emitting event to the target user's socket
emit(event, payload) on user's socket.The problem here is that the reference is saved on the worker, so if I have 4 workers, would these 4 workers be able to see the all the websockets?
I'm not sure if I have been able to explain this clear enough, but I think the point is that each worker is it's own process, and each worker have their own reference to the sockets of the users.
UNLESS this problem does not exists in ws and nodejs clusters, but how?
This is purely hypothetical but I've research on this and sure enough there are people who've had this problem. They talk about using redis (which adds unnecessary cost), basically store the reference to the sockets in redis so that each processes have this one database where they can get the user's socket.
I've thought of saving the reference to all the sockets in the master process, something like this:
const cluster = require('cluster');
if (cluster.isMaster) {
const users = {};
for (let a = 0, maxA = require('os').cpus().length; a < maxA; a++) {
const worker = cluster.fork();
worker.on('message', ({ action, payload }) => {
console.log(action, payload);
switch (action) {
case 'emitSocketEvent':
if (payload.userId in users) {
console.log(users[payload.userId]);
users[payload.userId].forEach(socket => {
socket.send(payload.message);
});
}
break;
case 'saveSocket':
if (payload.userId in users) users[payload.userId].push(payload.socket);
else users[payload.userId] = [payload.socket];
break;
// .. handle other actions
}
});
}
} else {
console.log('worker', process.pid);
setTimeout(() => {
const userId = Math.random();
process.send({
action: 'saveSocket',
payload: {
userId,
socket: {
send: payload => {
console.log('send event', payload);
}
}
}
});
setTimeout(() => {
process.send({
action: 'emitSocketEvent',
payload: {
userId,
message: {
event: 'test event',
data: 'test data'
}
}
})
}, 1000);
}, 1000);
}
But this one will not work because I can't pass in functions as part of the payload in the process.send... In the example above, the send method of payload.socket won't get through and payload.socket would just be an empty object when it gets to the master. So I don't know
¯\_(ツ)_/¯ If this didn't work, I don't know how saving those references to redis will work.
Anyone have any idea how to implement this, please let me know. Thank you.
Most helpful comment
Hmm..
I see in the documentation:
Server sending broadcast data
var WebSocketServer = require('ws').Server
, wss = new WebSocketServer({ port: 8080 });
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
client.send(data);
});
};