Scaling socket.io with NestJS or NodeJS and Redis using Pub/Sub model
Let’s say you have a few services that are server that connects with clients using socket.io library. I’ll call these services as socket services as they are used to connect with the client.
Now you have services(be it any service, socket or not) that wants to send a message to a user.
Now, socket-2 service cannot directly send a message to case3 as he is not connected to socket-2 service. The reward service also cannot send message to case3 as it doesn’t even know the protocol or what is even socket.io
You can also see the case3 and socket-1 service are using socket.io version 1.4 and whiterose and socket-2 service are using socket.io version 4.0. So case3 cannot connect to socket-2 and same for whiterose.
So in this scenario, you have various options. First option is to use a message broker like RabbitMQ. Another is to do an gRPC call. Something like this:
So we have to manage the logic of which server this message should be delivered to and how to deliver this message.
Another solution for this is to use Pub/Sub model that socket.io supports natively with redis. Something like:
Now this actually broadcast the message to all of the connected socket services and the correct service picks up the message and send it to the respective client.
The code in NestJS to implement this is. First have an adapter that is just a pub/sub client with redis.
import {IoAdapter} from '@nestjs/platform-socket.io';
import {RedisClient} from 'redis';
import {ServerOptions} from 'socket.io';
import {createAdapter} from 'socket.io-redis';
const pubClient = new RedisClient({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
});
const subClient = pubClient.duplicate();
const redisAdapter = createAdapter({pubClient, subClient});
export class RedisIoAdapter extends IoAdapter {
createIOServer(port: number, options?: ServerOptions): any {
const server = super.createIOServer(port, options);
server.adapter(redisAdapter);
return server;
}
}
Then attach this adapter to our NestJS app.
app.useWebSocketAdapter(new RedisIoAdapter(app));
For NodeJS you have to manually attach adapter to socket.io. The documentation of socket.io has support for this here.
EXTRA: We have a problem => The message send by the socket-2 service will have a very different format because it uses a different encoding and while socket-1 service will receive it, it will discard it as the message is not readable because of version and implementational mismatch. If socket-1 service and socket-2 service were using the same socket.io version then our solution was perfect.
Now if you also have a version mismatch issue read the next article: Using Latest socket.io version with older library support. Here I have explained how to scale different versions using a cleaver hack.