Eggjs 使用 Socket.io 在正式环境多进程下无法收到消息
这种问题一般在开发的时候不会发现,当你发到正式环境,用 npm run start
运行项目的时候,就会莫名其妙的收不到消息。
最初的方式
我是这样配置 Socket 的:
按照官方的文档: https://www.wenjiangs.com/doc/cqnkmxn27n#EDE42gsk ,创建对应的控制器和中间件,在 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 的 多进程模型和进程间通讯: https://www.wenjiangs.com/doc/16cq6yjley 介绍的方法,在需要给某个客户端发送消息时,先给 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。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: ALS 复刻 - P5
下一篇: 谈谈自己对于 AOP 的了解
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论