阿里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的核心啟動也就不再神祕了。