关于IPC-Message通信

2,476 阅读2分钟

阿里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的核心启动也就不再神秘了。