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的初始化/重启等工作。
进程守护
异常可以简单分为两类,第一类是可以监听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 | |
+----------------------------------------->|
-
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也不受影响
- Master收到
agent-start
通知fork多个App Worker,这里的fork用的是cfork包,负责 worker 的启动,状态监听以及 refork 操作,保证worker的数量 - 多个App worker 启动成功后发送
app-start
事件通知到master - 所有的进程初始化成功后,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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。