egg多进程模型注意事项梳理

0次阅读

共计 2405 个字符,预计需要花费 7 分钟才能阅读完成。

背景

最近在项目中使用 egg 进行服务端开发,在开发过程中遇到了比较诡异的问题,具体表现为 mq 在监听到信息时,其回调函数会被多次执行,那么这会导致某个文件被同时操作等问题。

问题成因

这边梳理 egg 文档时,重点过了一下 egg 多进程的设计模式,了解到 egg 的 master-agent-worker 模式,那么这里面有些问题是需要我们在开发时去注意的了。

首先介绍下 egg 的多进程实现方式

egg 通过 node 提供的 cluster 实现了多进程模式,为了更好地利用多核环境,egg 一般会启用相当于 cpu 核数的 worker,以此来最大化利用 cpu 能力。

在 egg 启动时,master,agent,worker 的关系如图所示

+---------+           +---------+          +---------+
|  Master |           |  Agent  |          |  Worker |
+---------+           +----+----+          +----+----+
     |      fork agent     |                    |
     +-------------------->|                    |
     |      agent ready    |                    |
     |<--------------------+                    |
     |                     |     fork worker    |
     +----------------------------------------->|
     |     worker ready    |                    |
     |<-----------------------------------------+
     |      Egg ready      |                    |
     +-------------------->|                    |
     |      Egg ready      |                    |
     +----------------------------------------->|

在这种模式下,master、agent、worker 各司其职,主要制作分配如下:
master: 负责维护整个应用稳定性,当有 worker 因异常而退出时,master 负责拉起新的 worker,以确保应用正常运行。
agent: 由于 egg 的多进程模型会在每个进程中运行一份我们的应用实例,那么在某些情况下,这种机制会导致问题。比如,保存日志的逻辑如果在每个进程中都执行的话,那么在触发日志保存操作的时候,会有多个进程同时操作日志文件,那么此时就会导致文件读写问题。所以 egg 设计了 agent 进程,agent 进程只会有一个,不会出现上述问题,这样,对于类似上述的后台运行的逻辑就统一放到 agent 中去处理了。
worker: 负责执行业务代码,处理用户请求和定时任务,egg 在框架层保证了定时任务只会在单个 worker 中执行,所以可以放心使用。

分析 egg 多进程导致的问题

上面我们分析过了 egg 的多进程机制,所以我们知道了问题成因,出现我们最开始说的问题的原因是我们把 mq 的监听和处理逻辑放到了 worker 中,那么这样的话在实际运行过程中,就会导致 mq 收到消息时,回调函数被执行多次。

 到这里我们已经知道如何优化了,那就是把 mq 的处理逻辑放到 agent 中,以确保 mq 消息的回调仅执行一次。但是细心地你肯定发现了,这里有个问题,agent 只有一个实例,如果事情在 agent 里面做,那么不是无法利用多核性能了吗?

agent 与 worker 通信

的确,我们可以在 agent 中处理仅需要单次执行的逻辑,但是这样做就没法利用多核性能了。那么有什么办法吗?没错,就是进程间通信,具体思路就是,agent 还是负责 mq 的连接和监听逻辑,但是回调函数不在 agent 中执行,而是写在 worker 里面。那么 worker 什么时候执行这个逻辑呢?答案是,agent 通过进程间通信通知 worker。egg 内部实现了一个进程间通信机制,我们直接调用即可,主要实现方式如下:

 广播消息:agent => all workers
                  +--------+          +-------+
                  | Master |<---------| Agent |
                  +--------+          +-------+
                 /    |     \
                /     |      \
               /      |       \
              /       |        \
             v        v         v
  +----------+   +----------+   +----------+
  | Worker 1 |   | Worker 2 |   | Worker 3 |
  +----------+   +----------+   +----------+

指定接收方:one worker => another worker
                  +--------+          +-------+
                  | Master |----------| Agent |
                  +--------+          +-------+
                 ^    |
     send to    /     |
    worker 2   /      |
              /       |
             /        v
  +----------+   +----------+   +----------+
  | Worker 1 |   | Worker 2 |   | Worker 3 |
  +----------+   +----------+   +----------+

这里我们可以看出来,进程间通信都是基于 master 转发的,所以我们可以利用 egg 提供的机制,解决我们的问题。

解决办法

如上文分析,我们把 mq 的连接和监听逻辑放到 agent 中,当接收到消息时,通过进程间通信把通知发送给 worker,然后由 worker 执行具体的业务逻辑即可。具体代码其实可以参考 vue 的事件机制,在 worker 中监听指定事件:

app.messenger.on(action, data => {// 执行业务逻辑});

在 agent 中建立 mq 连接并监听消息,收到消息后触发事件:

exports.task = async ctx => {
  ...// 收到 mq 消息的逻辑此处省略
  // 准备发送通知
  ctx.app.messenger.sendRandom(action);
};

注意,需要单次执行的任务要调用 sendRandom 方法,这个是发送给一个 worker 的方法。当然,如果要执行多次的,可以调用 app.messenger.sendToApp() 方法,这个方法会把消息发送给所有 worker,并执行多次处理逻辑。

总结

egg 在多进程模型中的使用还是需要有一些技巧的,所以需要我们先熟悉 egg 的多进程机制后再进行业务开发,避免遇到奇怪的坑,浪费时间。

正文完
 0