1

Cluster

javscript的代码只能运行在单线程中,也就是一个nodejs进程只能运行在一个cpu上。如果需要充分利用多核cpu的并发优势,可以使用cluster模块。cluster能够创建多个子进程,每个进程都运行同一份代码,并且监听的是同一个端口。

简单利用Cluster fork cpu个数子进程的代码如下:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // 如果是Master则进行fork操作,启动其他进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', function(worker, code, signal) {
    console.log('worker ' + worker.process.pid + ' died');
  });
} else {
  // 否则启动http服务监听
  http.createServer(function(req, res) {
    res.writeHead(200);
    res.end("hello world\n");
  }).listen(8000);
}
  • 为什么cluster fork多份源码跑在多个子进程上没有报端口被占用?
    cluster模块会hack掉worker中的监听,端口仅由master的TCP服务监听了一次

  • Master是如何处理请求转发的worker的?
    所有请求会统一经过内部TCP服务,符合一定负载均衡的挑选出一个worker并发送内部消息,该worker接收到消息后执行具体业务逻辑。(除 Windows 之外所有平台上的默认方法是循环方法,接受新的连接,并以循环方式将它们分发给各个工作线程,同时使用一些内置的智能,来实现负载均衡。)

EGG框架中的多进程

Agent机制

在eggjs中,除了有worker还有Agent,实际上也是一个worker,为了区别把他们称为agent worker和app worker。
Agent worker的作用是用来处理一些后台运行逻辑,比如说打印日志,不需要在4个app worker上都去执行,不对外提供服务,只处理公共事务,所以稳定性相对来说是很高的。

                +--------+          +-------+
                | Master |<-------->| Agent |
                +--------+          +-------+
                ^   ^    ^
               /    |     \
             /      |       \
           /        |         \
         v          v          v
+----------+   +----------+   +----------+
| Worker 1 |   | Worker 2 |   | Worker 3 |
+----------+   +----------+   +----------+

Master-Agent-Worker模型下,master承担了类似于pm2的进程管理的职责,能够完成worker的初始化/重启等工作。
image.png

进程守护

异常可以简单分为两类,第一类是可以监听process.on('uncaughtException', handler)捕获的异常,通过监听事件可以使得进程不会异常推出还有机会可以继续执行。第二类是被系统杀死直接推出的异常。
eggjs使用了graceful和egg-cluster让异常发生时master能够立刻fork出一个新的worker保持连接的worker数。

egg-cluster

进程启动顺序

+---------+           +---------+          +---------+
|  Master |           |  Agent  |          |  Worker |
+---------+           +----+----+          +----+----+
     |      fork agent     |                    |
     +-------------------->|                    |
     |      agent ready    |                    |
     |<--------------------+                    |
     |                     |     fork worker    |
     +----------------------------------------->|
     |     worker ready    |                    |
     |<-----------------------------------------+
     |      Egg ready      |                    |
     +-------------------->|                    |
     |      Egg ready      |                    |
     +----------------------------------------->|
  1. Master 启动后先 fork Agent 进程,同时监听'agent-exit, agent-start'事件,agent 启动成功后发送agent-start事件(IPC进程间通信)通知master

    • master监听到agent-exist事件会在一秒后再fork一次agent worker,保持agent稳定onAgentExit
    • agent-start事件为once,即使重启了agent app worker也不受影响
  2. Master收到 agent-start 通知fork多个App Worker,这里的fork用的是cfork包,负责 worker 的启动,状态监听以及 refork 操作,保证worker的数量
  3. 多个App worker 启动成功后发送app-start事件通知到master
  4. 所有的进程初始化成功后,Master 通知 Agent 和 Worker 应用启动成功

IPC进程通信

在nodejs中实现进程通信可以通过监听messgae事件实现

'use strict';
const cluster = require('cluster');

if (cluster.isMaster) {
  const worker = cluster.fork();
  worker.send('hi there');
  worker.on('message', msg => {
    console.log(`msg: ${msg} from worker#${worker.id}`);
  });
} else if (cluster.isWorker) {
  process.on('message', (msg) => {
    process.send(msg);
  });
}

在eggjs Agent机制中,agent也是也给worker,所以IPC通道存在与master和agent/app worker之间,而agent和app worker之间的通信需要通过master转发。
Eggjs包装了Message类,用from to标记涞源和去向。

this.messenger.send({
      action: 'agent-exit',
      data: { code, signal },
      to: 'master',
      from: 'agent',
    });

参考链接

https://juejin.im/entry/59bcce1b5188257e82676b53
https://zhuanlan.zhihu.com/p/49276061
https://segmentfault.com/a/1190000018894188
https://eggjs.org/zh-cn/core/cluster-and-ipc.html


Obeing
665 声望108 粉丝

努力地成为一只小牛