最近在项目中使用egg进行服务端开发,在开发过程当中遇到了比较诡异的问题,具体表现为mq在监听到信息时,其回调函数会被屡次执行,那么这会致使某个文件被同时操做等问题。vue
这边梳理egg文档时,重点过了一下egg多进程的设计模式,了解到egg的master-agent-worker模式,那么这里面有些问题是须要咱们在开发时去注意的了。node
egg经过node提供的cluster实现了多进程模式,为了更好地利用多核环境,egg通常会启用至关于cpu核数的worker,以此来最大化利用cpu能力。设计模式
在egg启动时,master,agent,worker的关系如图所示app
+---------+ +---------+ +---------+ | 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的多进程机制,因此咱们知道了问题成因,出现咱们最开始说的问题的缘由是咱们把mq的监听和处理逻辑放到了worker中,那么这样的话在实际运行过程当中,就会致使mq收到消息时,回调函数被执行屡次。dom
到这里咱们已经知道如何优化了,那就是把mq的处理逻辑放到agent中,以确保mq消息的回调仅执行一次。可是细心地你确定发现了,这里有个问题,agent只有一个实例,若是事情在agent里面作,那么不是没法利用多核性能了吗?
的确,咱们能够在agent中处理仅须要单次执行的逻辑,可是这样作就无法利用多核性能了。那么有什么办法吗?没错,就是进程间通讯,具体思路就是,agent仍是负责mq的链接和监听逻辑,可是回调函数不在agent中执行,而是写在worker里面。那么worker何时执行这个逻辑呢?答案是,agent经过进程间通讯通知worker。egg内部实现了一个进程间通讯机制,咱们直接调用便可,主要实现方式以下:async
广播消息: 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的多进程机制后再进行业务开发,避免遇到奇怪的坑,浪费时间。