Scaling SockJS keeping track of clients - node.js

Im in the process of scaling up my Nodejs server which runs a SockJS server. Its a basic chat where the SockJS server stores each connected client.
Basically i store it like this:
var clients = [];
var sockets = {};
// Identify this client
socket.name = socket.remoteAddress + ":" + socket.remotePort
// Put this new client in the list
clients.push(socket);
sockets[socket.name] = socket;
This is how i write to the socket:
sockets[socketName].write("{\"type\":\"type\", \"msg\": \"" + escp(obj.msg) + "\", \"name\": \"" + obj.name+ "\", \"location\": \"" + obj.location + "\"}");
My idea is to now scale up and start more instances on Amazon. And i'll have HAProxy in the front balacing the load.
The problem is that both people chatting would have to be on the same instance, since the connectedClients isent shared amongst my ec2 instances.
Any ideas how to accomplish this? Do i need to store connectedClients in a db? (Is that even possible)?
Basically i need to share sockets and clients between my instances

I think you'll probably have to store the session connection in redis/mongodb and have those nodes server do pub/sub to the redis in order to have consistent request /response across the node. therefore, when user send message to HAProxy irrespective of which server the HAProxy route the request to the node has check-in redis if there is existing connection.

Related

Can I run a node.js server on a VPS but broadcast events to it from a go daddy hosted laravel backend?

I have an app with a backend currently hosted on godaddy. The backend is Laravel 5.4 and the front end is Ionic 2. I want to add a live chat to the existing app. I will use socket.io and redis. All of the chat messages will be saved to my database (on godaddy). Go daddy doesn't allow running a node server unless you buy a vps but they charge too much
I was wondering if this setup is possible.
A user sends a message
The message is sent via a POST request from my frontend to my backend to be saved in the database.
Once the message is saved, a broadcast message/event is sent from laravel to the frontend
Now my question is can I use something like A2 hosting ($5 for a vps) to ONLY host my socket.io & redis server. So basically I want to know will I be able to broadcast messages from laravel (go daddy) to A2 hosting, and have my frontend listening to the A2 server? Or do I have to host my whole backend on A2 hosting because I can't broadcast messages to A2 hosting unless a server is also running on go daddy
Hopefully what I'm asking makes sense
My server.js file (in laravel root)
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var Redis = require('ioredis');
var redis = new Redis();
redis.psubscribe('private-chat.*', function(err, count) {
console.log('psubscribe');
});
redis.on('pmessage', function(subscribed, channel, message) {
console.log('pmessage', subscribed, channel, message);
message = JSON.parse(message);
io.emit(channel + ':' + message.event, message.data);
});
http.listen(3000, function(){
console.log('Listening on Port 3000');
});
Sending the broadcast
// save it to the database and than
event(new NewMessagePosted($message));
Broadcasting to the channel
public function broadcastOn()
{
return new PrivateChannel('chat.'. $this->message->chatID);
}
If I understand the question, then you want to know if you must host everything on A2 in order for your chat application to work. The answer is no. I have a node socket server running on its own that emits messages to several applications hosted on DigitalOcean, AWS and Heroku. As long as everything is setup and secure and each socket client is listening for the correct events from the remote socket server, I don't see the issue.

Horizontal scale of nodejs TCP Server

I'm trying to figure out how to horizontally scale my nodejs TCP Server.
Example server:
var socketServer = net.createServer(function (socket) {
// Identify this client
socket.name = socket.remoteAddress + ":" + socket.remotePort
// Put this new client in the list
clients.push(socket);
sockets[socket.name] = socket;
// Handle incoming messages from clients.
socket.on('data', function (data) {
// Write to a specific user
var socketName = "xx.xx.xx.xx:56512";
sockets[socketName].write("Hello!");
}
}).listen(8080);
The following example stores each new connection in an array so that we can write a message to a specific user down the road.
I would like to scale this with haproxy as the loadbalancer. But how can i get all of my servers to share the same array of connected clients?
Stickysessions is not a solution that would work for me, since two people talking (two clients) could be on different servers, so all servers somehow have to share the same array of connected clients.
Any ideas how this can be done?
An idea i had:
Is it maybe possible to store each connection in MySQL?
Edit
Maybe the only way of actually doing this is by using Redis?
Any ideas if that would be a suitable option? The load is going to be extremely heavy
1.Use Redis to share your session with your servers.
2.Write a Login-Server,all users should login in Login-Server.
firstly.Login-server will generate a session,and share session on Redis-server and return transaction server ip and port,
Secondly,Client receive ip and port, disconnect Login-server, and connect to the transaction server,
Lastly,You can provide several TCP servers to handle transaction,Login-server will balance load through ip and port. of course, you can use haproxy to do that.but i don't know how to configure it.

node.js + socket.io + redis architecture - horizontal serverscaling socket connections?

i'm using node.js for the first time and hoping for an advice:
i installed the following programs on my server:
node.js v0.11.3-pre
express v3.3.4
socket.io v0.9.14
connect-redis v1.4.5
Redis server v=2.6.14
redis-cli 2.6.14
First of all, i created an express app:
express testApplication
In the created "package.json" i defined all neccessary depencies.
From the start i defined a cluster for vertically scaling (multi-processes) in a file called "cluster.js":
var cluster = require('cluster');
if( cluster.isMaster ) {
var noOfWorkers = process.env.NODE_WORKERS || require('os').cpus().length;
console.log("Workers found: " + noOfWorkers);
for (var i = 0; i < noOfWorkers; i += 1) {
cluster.fork();
}
} else {
require('./app.js');
}
cluster.on('exit', function(worker, code, signal) {
var exitCode = worker.process.exitCode;
console.log('worker' + worker.process.pid + ' died (' + exitCode + '). restarting...');
if( typeof cluster.workers[worker.id] != "undefined" )
cluster.workers[worker.id].delete();
cluster.fork();
});
In my "app.js" file i defined REDIS for socket.io storing:
io.set('store', new RedisStore({
redisPub: pub,
redisSub: sub,
redisClient: client
}));
So far, so good, all this works pretty nice.
When a client connects to the socket.io server, the cluster handles the connections with different workers.
My intention is, that a client can send a message to a specific another client, so the socket.io server have to find the socket from the receipient to send the message only to this user. The solution for me is, that i store all created socket ids for every user in an array and when sending a message, i select the relevant socket ids in the array, gets the sockets by id, and send the message to the socket(s).
This works very fine for a socket.io application, which is running only on one server.
Now, i want to configure another server with the same programs, modules and packages.
The load balancing will probably be handled by HAProxy. So the socket.io connections (sockets) will be stored and managed on Server A and Server B.
Example scenario:
User A connects to Server A and User B connects to Server B. That means, that User A has a socket on Server A und User B on Server B.
How is it possible, that the application knows, that it has to look for the socket of User B on Server B to send the message? On Server A it won't find the socket, because it was created on Server B.
Thx a lot!
When you're horizontally scaling, you need to share a datastore between your servers. Conveniently, you already have an ideal one, which is Redis. Instead of keeping your socket mapping in an array, you need to push it into Redis, and do lookups from there.
Then, you can either decide to have servers send messages to each other, or, more scalably, send the messages through Redis as well. So, each server would look at it's queue on Redis to see what messages it should send to which sockets. When a server receives a message, it will push it into Redis addressed to the server that should deliver it. If the ordering of messages matters to you, I would recommend looking at https://github.com/learnboost/kue as layer on top of Redis.
Don't forget to include NGINX in front of NodeJS and use PM2!

Generating socket.io at Server side as Rest request

There is a way to manage the socket.io creation at Server Side?, Currently, I couldn't found any doc, Only found in relation with the socket is created per request from a client "io.connect(server)".
The current flow work OK:
Set Socket.io (at Node.js) at SERVER:PORT
Client connect to SERVER
using io.connect(SERVER:PORT)
I wonder if it is possible ? Trying to do:
Set Socket.io (at Node.js) at SERVER:PORT
Recieved a POST (REST) - Server side
Create/Open Socket.io a server side.
At response of Post send the id?
the clien open a socke.io
Sent to client socket.id to client as
So Far, looking in deep on the code and doc, I found that socket.io support namespaces, so I used this in order to manage client connection id.
at server.js
var app = express();
var server = require('http').createServer(app),
io = require('socket.io').listen(server,{ log: false });
// Rest New Process
function generateNameSpaceWs (responce, request) {
io.of("/" + id).on('connection', handler);
response.send(id);
}
app.post("/newWS", function (res, req) {
return generateNameSpaceWs(res, req);
}
at Client.js
function makeWS(){
var ws, c = new XMLHttpRequest();
c.open("GET", url, false);
c.send();
if (c.status == 200){
id = JSON.parse(c.responseText);
ws = new io.connect("server/" + id)
}
So far you are doing right, if I understand your question correctly, you are trying to authenticate connection via POST, so that user can only connect to server via socket if server responds to ID. This is a roundabout way. Use the socket instead of POST.
Socket server has to be running already, and accepts connection via io.sockets.on('connection'), at server you can choose whether to accept it or reject it ,do socket.disconnect('unauthorized') to close connection from server.
I would you suggest you do this :
Set Socket.io (at Node.js) at SERVER:PORT
Client connect to SERVER using io.connect(SERVER:PORT)
Send what you are sending in POST over socket.
Authenticate/Process on io.sockets.on('connection', function(socket) at server.
Close socket if unathorized.
Send back ID data to client.
This doesn't seem possible -- while the official documentation for socket.io is lacking, the documentation for the net module indicates that the only way to create a socket is to initiate it server side.
However, you can still achieve the desired effect by creating an id for the socket on the server to associate with the socket. That is,
Set Socket.io (at Node.js) at SERVER:PORT
Recieved POST (REST) - Server side
Create id (Note:This could be done before step 2)
At response of Post send the id!
Client connect to SERVER
using io.connect(SERVER:PORT)
The client sends the id to the server using something like
socket.emit("set_id",id)
The server recieves the id and associates it with the socket using something like
socket.on("set_id",function(id){
socket.set("id",id)
}
Now you can reference the socket using the id that you created!
Good luck!

I'm receiving duplicate messages in my clustered node.js/socket.io/redis pub/sub application

I'm using Node.js, Socket.io with Redisstore, Cluster from the Socket.io guys, and Redis.
I've have a pub/sub application that works well on just one Node.js node. But, when it comes under heavy load is maxes out just one core of the server since Node.js isn't written for multi-core machines.
As you can see below, I'm now using the Cluster module from Learnboost, the same people who make Socket.io.
But, when I fire up 4 worker processes, each browser client that comes in and subscribes gets 4 copies of each message that is published in Redis. If there are are three worker processes, there are three copies.
I'm guessing I need to move the redis pub/sub functionality to the cluster.js file somehow.
Cluster.js
var cluster = require('./node_modules/cluster');
cluster('./app')
.set('workers', 4)
.use(cluster.logger('logs'))
.use(cluster.stats())
.use(cluster.pidfiles('pids'))
.use(cluster.cli())
.use(cluster.repl(8888))
.listen(8000);
App.js
redis = require('redis'),
sys = require('sys');
var rc = redis.createClient();
var path = require('path')
, connect = require('connect')
, app = connect.createServer(connect.static(path.join(__dirname, '../')));
// require the new redis store
var sio = require('socket.io')
, RedisStore = sio.RedisStore
, io = sio.listen(app);
io.set('store', new RedisStore);io.sockets.on('connection', function(socket) {
sys.log('ShowControl -- Socket connected: ' + socket.id);
socket.on('channel', function(ch) {
socket.join(ch)
sys.log('ShowControl -- ' + socket.id + ' joined channel: ' + ch);
});
socket.on('disconnect', function() {
console.log('ShowControll -- Socket disconnected: ' + socket.id);
});
});
rc.psubscribe('showcontrol_*');
rc.on('pmessage', function(pat, ch, msg) {
io.sockets.in(ch).emit('show_event', msg);
sys.log('ShowControl -- Publish sent to channel: ' + ch);
});
// cluster compatiblity
if (!module.parent) {
app.listen(process.argv[2] || 8081);
console.log('Listening on ', app.address());
} else {
module.exports = app;
}
client.html
<script src="http://localhost:8000/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script>
var socket = io.connect('localhost:8000');
socket.emit('channel', 'showcontrol_106');
socket.on('show_event', function (msg) {
console.log(msg);
$("body").append('<br/>' + msg);
});
</script>
I've been battling with cluster and socket.io. Every time I use cluster function (I use the built in Nodejs cluster though) I get alot of performance problems and issues with socket.io.
While trying to research this, I've been digging around the bug reports and similar on the socket.io git and anyone using clusters or external load balancers to their servers seems to have problems with socket.io.
It seems to produce the problem "client not handshaken client should reconnect" which you will see if you increase the verbose logging. This appear alot whenever socket.io runs in a cluster so I think it reverts back to this. I.E the client gets connected to randomized instance in the socket.io cluster every time it does a new connection (it does several http/socket/flash connections when authorizing and more all the time later when polling for new data).
For now I've reverted back to only using 1 socket.io process at a time, this might be a bug but could also be a shortcoming of how socket.io is built.
Added: My way of solving this in the future will be to assign a unique port to each socket.io instance inside the cluster and then cache port selection on client side.
Turns out this isn't a problem with Node.js/Socket.io, I was just going about it the completely wrong way.
Not only was I publishing into the Redis server from outside the Node/Socket stack, I was still directly subscribed to the Redis channel. On both ends of the pub/sub situation I was bypassing the "Socket.io cluster with Redis Store on the back end" goodness.
So, I created a little app (with Node.js/Socket.io/Express) that took messages from my Rails app and 'announced' them into a Socket.io room using the socket.io-announce module. Now, by using Socket.io routing magic, each node worker would only get and send messages to browsers connected to them directly. In other words, no more duplicate messages since both the pub and sub happened within the Node.js/Socket.io stack.
After I get my code cleaned up I'll put an example up on a github somewhere.

Resources