Egg 源码分析之 egg-cluster

接着前篇关于 egg-core 源码分析的文章 Egg 源码分析之egg-core ,今天来看一下 egg-cluster 的源码实现逻辑。

NodeJs 中 javascript 的执行是单线程的,所以一个进程只能使用一个 CPU,为了最大可能的使用服务器资源,一般我们可以使用下面三种方式实现:

  • 同一台机器上部署多个 Node 服务,使用不同的端口,然后用 Nginx 做负载均衡将请求转发到不同的 Node 实例;
  • 使用 PM2 进程管理工具,多个进程共用一个端口,PM2 负责进程重启工作;
  • 利用 Node 自带 child_processcluster 模块可以方便实现多进程之间的通信;

egg-cluster 是什么

egg-cluster 是 Egg 的多进程模型的启动模式,在使用 cluster 模式启动 Egg 应用时,我们只需要配置相关启动参数, egg-cluster 会自动创建相关进程,并管理各个进程之间的通信以及异常处理等问题。主进程 Master 通过 child_process 模块的 fork 函数创建 Agent 子进程,并通过 cluster 模块创建 Worker 子进程。Master/Agent/Worker 三者各司其职,共同保证 Egg 应用的正常运行:

  • Master:

Master 进程只有一个且第一个启动,主要负责进程管理的工作,包括 Worker、Agent 进程的初始化和重启以及进程之间的通信工作。Master 不运行任何业务代码,它的稳定性特别重要,一旦挂掉整个 Node 服务就挂掉了;

  • Agent

Agent 进程也只有一个,一般在业务开发时我们不太会用到 Agent 进程,它的用处主要有两方面:(1)如果想让你的代码只在一个进程上运行(2)Agent 进程可以将某个消息同时广播到所有 Worker 进程进行处理;

  • Worker

Worker 进程根据用户自己的设定可以有多个,主要负责处理业务逻辑和用户的请求。当 Worker 进程异常退出时,Master 进程会重启一个新的 Worker 进程;

Agent 子进程是 Egg.Agent 类的实例,Worker 子进程是 Egg.Application 的实例,而 Egg.Agent 和 Egg.Application 都是 EggApplication 的子类,而 EggApplication 类又是 EggCore 的子类,关于 EggCore 的源码实现可以看一下我前面的文章 Egg 源码分析之egg-core,所以类与实例之间的关系图如下:

                            +--------------+    实例    +--------------+
                            | Agent 子进程  | --------> | Agent 类      |
                            +--------------+           +---------------+ 
                          /                                               \
     child_process.fork /                                                   \ 继承
                      /                                                       \
         +---------------+                                                      +-------------------+    继承  +------------+
         | Master 主进程  |                                                      | EggApplication 类 | ------>  | EggCore 类 |
         +-------------- +                                                      +------------------ +          +------------+ 
                      \                                                        /  
                        \                                                     / 继承
             cluster.fork \                                                  /
                            +---------------+   实例     +----------------+
                            | Worker 子进程  | ------->  | Application 类  | 
                            +---------------+           +-----------------+ 


egg-cluster 源码分析

egg-cluster 整个模块的入口是 master.js,它的初始化流程如下:

  1. workerManager 实例和 messenger 实例的初始化
  2. 自动探测及获取可用的 clusterPort,使用 cluster-client 让 Agent 和 Worker 直接通信变为可能
  3. 启动 Agent 子进程
  4. 启动 Worker 子进程
  5. 启动完毕,实时监测各个进程服务状态
// egg-cluster 源码 -> 启动流程(为了更容易看清楚初始化流程,constructor函数中有些代码先后顺序做了调整)
// Master 继承了 EventEmitter模块,通过事件的监听和订阅方式,非常方便的进行事务处理
class Master extends EventEmitter {
   
  constructor(options) {
   
    super();
    //步骤1
    this.workerManager = new Manager();   // workManager 是一个进程管理的工具,记录当前各个进程的状态信息
    this.messenger = new Messenger(this); // messenger 主要负责进程之间的通信工作
 
    //步骤2:自动探测及获取可用的 clusterPort
    detectPort((err, port) => {
   
      this.options.clusterPort = port;
      //步骤3. 启动 Agent 子进程
      this.forkAgentWorker();
    });
    
    //步骤4. 启动 Worker 子进程
    this.once('agent-start', this.forkAppWorkers.bind(this));
    
    //步骤5:通知启动完毕,实时监测子进程服务状态
    this.ready(() => {
   
      this.isStarted = true;
      const action = 'egg-ready';
      //通过 messenger 的 send 函数通知服务已经启动就绪
      this.messenger.send({
    action, to: 'parent', data: {
    port: this[REALPORT], address: this[APP_ADDRESS] } });
      this.messenger.send({
    action, to: 'app', data: this.options });
      this.messenger.send({
    action, to: 'agent', data: this.options });
      //使用 workerManager.startCheck 函数定时监控进程状态
      if (this.isProduction) {
   
        this.workerManager.startCheck();
      }
    });
  }
}
步骤1:子进程的管理和进程间的通信(manager 和 messenger)
  • manager

manager 实现比较简单,主要通过两个属性 workers 和 agent 来维护进程的状态信息,提供了多个函数用于获取,删除,设置相关进程。这里主要看一下监听进程存活状态的 startCheck 函数的实现:

// egg-cluster 源码 -> startCheck实现
class Manager extends EventEmitter {
   
  startCheck() {
   
    this.exception = 0;
    // 每隔 10s 钟监听 Worker 和 Agent 的数量
    this.timer = setInterval(() => {
   
      const count = this.count();
      // Agent 只有一个且必须处于存活状态,Worker 至少要有一个处于存活状态服务才可用
      if (count.agent && count.worker) {
   
        this.exception = 0;
        return;
      }
      this.exception++;
      // 如果连续三次检查发现服务都不可用就触发 exception 事件,master.workerManager 监听到该事件后会退出服务
      if (this.exception >= 3) {
   
        this.emit('exception', count);
        clearInterval(this.timer);
      }
    }, 10000);
  }
}
  • messenger

Worker 子进程与 Agent 子进程都可以通过 IPC 与 Master 进程进行通信,而 Worker 子进程与 Agent 子进程之间是无法直接通信的,必须通过 Master 进程作为中间桥梁进行通信。每个 Master/Agent/Worker 进程都有一个 messenger 实例,该 messenger 实例用于管理与其它进程的通信工作。

这里需要注意的是 Master.messenger 实例对应的 Messenger 类的定义是在 egg-cluster 源码 中,而 Agent.messenger 和 Worker.messenger 实例对应的 Messenger 类的定义是在 egg 源码 中。前者定义了主进程如何给子进程发

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值