關於IPC-Message通訊

51信用卡大前端團隊發表於2019-03-01

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

相關文章