Egg.js 使用 Socket.io 在正式环境多进程下无法收到消息
这种问题一般在开发的时候不会发现,当你发到正式环境,用 npm run start 运行项目的时候,就会莫名其妙的收不到消息。
最初的方式
我是这样配置 Socket 的:
按照官方的文档,创建对应的控制器和中间件,在 connection.js 中使用全局变量 global.userList 保存连接的句柄,这样在其他的 controller 和 service 里面也可以访问。
// app/io/middleware/connection.js
module.exports = app => {
return async (ctx, next) => {
// 用户连接
const socket = ctx.socket;
// 验证用户权限
try {
const auth = ctx.jwt.verify(ctx.query.token, app.config.jwt.secret);
ctx.auth = await ctx.service.user.getUserBy('id', auth.id);
} catch (err) {}
socket.user_id = ctx.auth.id;
global.userList.push(socket);
await next();
// 用户离线
for (let i = 0; i < global.userList.length; i++) {
if (global.userList[i].user_id === ctx.auth.id) {
global.userList.splice(i, 1);
break;
}
}
};
};但是放到线上,就不行了经常收不到消息,经过多方搜索问题,最终确实是多线程惹的祸,Eggjs 在运行正式环境的情况下,会开启多进程,具体的线程数根据 CPU 的个数确定,而我定义的 global 变量只能在当前线程生效。
再仔细查看官方文档以后,提到了这个问题,官方提到使用 egg-redis 实现 clients/rooms 等信息共享,这又是一套单独的服务,本着服务越少、Bug 越少的原则,决定使用其他方式实现 clients 同步。
项目上本身使用了 memcache 作为缓存工具,那就把连接的 clients 放到 memcache 里面,要用的时候从里面取。
最开始是想集中式管理连接句柄,但是除了内存,没有什么地方都够储存这种数据,memcache、数据库 都不行。
可行的方案
既然集中式管理不行,那就只有再每个进程上去找所有的连接句柄,通过调试发现,所有的连接句柄都在 this.app.io.sockets.sockets 上,还是上面的代码,在连接的时候将用户 ID 附加到连接句柄上,当在 this.app.io.sockets.sockets 上找的时候,根据 user_id 匹配。
利用 Eggjs 的 多进程模型和进程间通讯介绍的方法,在需要给某个客户端发送消息时,先给 app 进程发送消息,app 进程给每个 worker 发送消息查找客户端。
this.app.messenger.sendToApp('socketSend', {
user_id: sockets[i].user_id,
type: 'msg',
message,
});在 app.js 里面应用启动完成以后监听 worker 发送给 app 的消息:
async serverDidReady() {
const ctx = this.app.createAnonymousContext();
await ctx.service.memcache.set('sockets', []);
this.app.messenger.on('socketSend', data => {
ctx.service.socketHandler.socketSend(data.user_id, data.type, data.message);
});
}最后编写具体发送 service:
/* eslint-disable array-bracket-spacing */
'use strict';
const Service = require('egg').Service;
module.exports = class extends Service {
async socketSend(user_id, type, message) {
for (const key in this.app.io.sockets.sockets) {
if (Number(this.app.io.sockets.sockets[key].user_id) === Number(user_id)) {
this.app.io.sockets.sockets[key].emit(type, message);
break;
}
}
}
};
上面仅贴出了部分代码,如有没说清的地方请留言告知。使用这种方式,无法直观的查到到底有多少人在线,可以单独做一个统计计数,在用户连接时 +1,用户离线 socket 断开时 -1。
发布评论
评论列表 0





