socket.io多实例集群化实现
Socket.io作为服务器推送的首要选择,可以很方便的在客户端和Web服务器之间实现双向通信。很多人在选择之初都会有个疑问:socket.io是有状态长链接服务,它能支撑多少个用户同时在线?
虽然目前没有标准统一的答案,但是,在开发实践中已经证明,在单机情况下10万用户是没问题的。
如果您觉得10万还是不够,那么可以通过多实例的方式来支持更多的用户也是可行的。实现方式几乎是零成本升级。
您只需要将默认的适配器更换为redis或其他适配器就可以支持多实例:
一个基于epxress和socket.io的多实例服务配置:
#!/usr/bin/env node
const app = require('../app');
const debug = require('debug')('ws_server:www');
const { createServer } = require('http');
const { Server } = require('socket.io');
const { createAdapter } = require("@socket.io/redis-adapter");
const { Redis } = require("ioredis");
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const httpServer = createServer(app);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = httpServer.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
console.log('Listening on ' + bind);
}
// 重点部分
const pubClient = new Redis({
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT,
password: process.env.REDIS_PASSWORD,
});
const subClient = pubClient.duplicate();
const io = new Server(httpServer, {
adapter: createAdapter(pubClient, subClient),
cors: {
origin: ["https://admin.socket.io", "http://127.0.0.1:8080"],
// allowedHeaders: ["Authorization", "Cookie"],
credentials: true
}
});
app.set("IO", io);
httpServer.listen(port);
httpServer.on('error', onError);
httpServer.on('listening', onListening);
经过上面的改动后,所有的socket.io服务器实例可以共享一个redis实例来存储连接的状态信息。
假设部署了两个socektio服务器serverA和serverB,它们既充当了Web服务器,也充当了socket.io的通讯服务。 当客户端clientA连接到服务器serverA时,serverB接收到了restfult请求,需要向clientA推送消息,在不改动原代码的情况下依旧可以顺利完成。