阿里eggjs中有个核心就是egg-cluster来做基本的启动流程,里面的通信还是比较有意思的。仔细研究了下nodejs官方的cluster再加上eggjs的Agent理念,如果要保持通信,还是踩了不少的坑。这个坑其实来自cluster自身。如果是刚研究cluster,那么很多人都是被其迷惑,到底是如何监听,如何发送呢?
概念
先理解一些概念:
- master 主进程
cluster.isMaster确定的进程 - worker 子进程
cluster.isWorker确定的进程 - agent child_process 创建的进程
想要在这些进程上互相通信,我们需要理清楚发送的方式。
- worker与agent通信,需要master作为桥梁中转
- worker与master通信
- master与worker通信
- master与agent通信
- agent与master通信
- agent与worker通信,需要master作为桥梁中转
如何让其完美通信,我这里推荐一个库 github.com/cevio/ipc-m…。它能让你在无感知的情况下帮你绑定完毕所有的事件,同时打通消息通道。
创建
它是一个类,需要被继承后使用。
const IPCMessage = require('ipc-message');
module.exports = class NodeBase extends IPCMessage {
constructor() {
// If it is a `agent` type process, you need to set the parameter to `true`.
// super(true);
super();
// receive message from other processes.
this.on('message', msg => {
console.log(`[${this.type}] Receive Message:`, msg);
});
if (this.type === 'master') {
// do master ...
} else {
// do worker
}
}
}
我们通过绑定message事件来获得消息,通过registAgent来注册agent,通过cluster.fork()来创建子进程,当然这个创建是被自动监听的,你无需关心。
消息
很简单,他们在任意的代码中通过send方法来发送消息。比如如上的例子(假设已经设置来一个名字为staticAgent的agent和建立了4个worker,现在是在worker运行的代码上):
const base = new NodeBase();
base.send('staticAgent', 'worker-ready', {
a: 1,
b: 2
});
agent通过master的转发就收到了该信息
[staticAgent] agent receive message:
{
to: [19678],
from: 19679,
transfer: true,
action: 'worker-ready',
body: {
a: 1,
b: 2
}
}
你可以通过这个数据来解析,具体如何解决全靠个人想法了。
使用
我们来看2段实际代码
test/index.js
const IPCMessage = require('ipc-message');
const ChildProcess = require('child_process');
const path = require('path');
const cluster = require('cluster');
const Koa = require('koa');
const os = require('os');
class Nodebase extends IPCMessage {
constructor() {
super();
if (this.type === 'master') {
const agentWorkerRuntimeFile = path.resolve(__dirname, 'agent.js');
// 创建一个agent
const agent = ChildProcess.fork(agentWorkerRuntimeFile, null, {
cwd: process.cwd(),
stdout: process.stdout,
stderr: process.stderr,
stdin: process.stdin,
stdio: process.stdio
});
// 注册该agent
this.registAgent('agent', agent);
let cpus = os.cpus().length;
while (cpus--) {
// 根据内核数来创建子进程
cluster.fork();
}
} else {
// 以下为实际开发的代码
const app = new Koa();
app.use(async (ctx, next) => {
if (ctx.req.url === '/favicon.ico') return;
this.send('agent', '/test/agent', { a:1, c:3 });
ctx.body = 'hello world';
});
app.listen(3000, () => {
console.log('server start at 3000');
});
}
this.on('message', msg => {
console.log(`[${this.type}] onMessageReceive:`, msg);
});
}
}
const nodebase = new Nodebase();
if (nodebase.type === 'master') {
setTimeout(() => {
// 发送消息给agent
nodebase.send('agent', '/a/b', {
a:1
});
}, 5000)
}
test/agent.js
const IPCMessage = require('ipc-message');
class Agent extends IPCMessage {
constructor() {
// 如果是个agent启动的文件,这里必须为true
super(true);
this.timer = setInterval(() => {
console.log('agent alive');
}, 1000);
process.on('SIGINT', () => {
clearInterval(this.timer);
process.exit(0);
});
this.on('message', msg => {
console.log('[Agent] onMessageReceive:', msg);
this.send([msg.from, 'master'], '/reply/agent', 'done');
})
}
}
const agent = new Agent();
// 发送消息
agent.send('master', '/agent/ready', { a: 1, b: 2 });
最后
有了通信处理的简化模块,你也可以尝试使用其来创建类似egg的启动流程,egg的核心启动也就不再神秘了。