Eggjs 使用 Socket.io 在正式环境多进程下无法收到消息

发布于 2023-12-05 12:54:35 字数 2764 浏览 22 评论 0

这种问题一般在开发的时候不会发现,当你发到正式环境,用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84960 人气
更多

推荐作者

内心激荡

文章 0 评论 0

JSmiles

文章 0 评论 0

左秋

文章 0 评论 0

迪街小绵羊

文章 0 评论 0

瞳孔里扚悲伤

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文