Node.js集群Worker间通信方案及PM2子进程显示问题咨询
嘿,我刚好碰到过类似的情况!当你用pm2 start index.js -i 0启动时,PM2会完全接管主进程的角色——它自己成为master,然后fork你的应用代码作为它的worker进程。这就是为什么你代码里的cluster.isMaster永远不会触发,因为你的进程全是PM2的worker,根本轮不到你的代码当master。
下面分两部分给你讲可行的解决方案:
一、Worker间通信的几种靠谱方案
1. 用PM2自带的IPC通道(最省事)
PM2本身就提供了进程间通信的能力,甚至还有现成的pm2-intercom模块帮你做广播。只需要三步:
- 先安装模块:
npm install pm2-intercom - 在你的worker进程里,要广播消息时直接发特定格式的消息:
process.send({ topic: 'ws-broadcast', // 自定义频道名 data: '要推送给所有WebSocket客户端的内容' }); - 每个worker监听
message事件接收广播:process.on('message', (msg) => { if (msg.topic === 'ws-broadcast') { // 把消息推送给当前worker的所有WebSocket连接 wsServer.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(msg.data); } }); } });
这个模块会自动帮你把消息转发给所有PM2管理的worker,不用自己写中转逻辑,非常省心。
2. 用Redis发布/订阅(最稳定,支持分布式)
如果你的应用以后可能扩展到多台机器,或者不想依赖PM2的特性,用Redis的Pub/Sub是最佳选择。每个worker都订阅同一个频道,发消息时往频道里一扔,所有worker都能收到:
- 先装Redis客户端,比如
ioredis:npm install ioredis - 每个worker里的代码示例:
const Redis = require('ioredis'); // 分开创建订阅和发布客户端(避免同一连接同时读写的问题) const subscriber = new Redis(); const publisher = new Redis(); // 订阅广播频道 subscriber.subscribe('ws-broadcast'); subscriber.on('message', (channel, message) => { // 推送给当前worker的WebSocket客户端 wsServer.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(message); } }); }); // 封装广播函数 function broadcastWebSocketMessage(content) { publisher.publish('ws-broadcast', JSON.stringify(content)); }
这个方案完全解耦,不管你的worker是由PM2还是其他工具管理,甚至跨机器都能正常通信,适合长期维护的项目。
3. 自定义独立中转主进程(最灵活)
如果你想完全掌控通信逻辑,可以自己写一个独立的master进程(比如叫ws-relay.js),专门负责转发worker的消息。然后所有业务worker都连接到这个中转进程:
- 中转进程用
net模块或者IPC通道监听连接,收到消息后转发给所有连接的worker。 - 业务worker启动时主动连接到中转进程,发送和接收消息。
- 最后用PM2单独启动这个中转进程,再启动多个业务worker进程。
这种方案自由度最高,但需要自己处理进程间连接的稳定性、重连等逻辑,适合有特殊需求的场景。
二、不用PM2集群模式,让所有子进程显示在PM2列表里
当你用pm2 start index.js启动时,你的cluster模块fork的子进程不会被PM2纳入管理,所以PM2列表里只有你的master进程。要让所有子进程都显示,有两种方法:
1. 放弃自己的cluster逻辑,交给PM2管理所有实例
最简单的方法就是删掉你代码里的cluster相关逻辑,直接用PM2启动多个实例:
pm2 start index.js -i max # max会自动按CPU核心数启动
这样每个实例都是独立的worker,都会显示在PM2列表里,然后用上面提到的PM2 intercom或者Redis来实现通信。这种方式最符合PM2的设计理念,管理起来也最方便。
2. 用PM2 API手动注册子进程
如果你一定要保留自己的master逻辑,那可以在master进程里用PM2的API来启动worker,而不是用cluster.fork():
- 先装PM2的API模块:
npm install pm2 - 把你的worker逻辑单独抽出来放到
worker.js里,然后在master进程里启动:const pm2 = require('pm2'); const os = require('os'); console.log('Master ' + process.pid + ' has started.'); pm2.connect((err) => { if (err) { console.error('连接PM2失败:', err); process.exit(1); } // 按CPU核心数启动worker for (let i = 0; i < os.cpus().length; i++) { pm2.start({ script: './worker.js', name: `ws-worker-${i}`, exec_mode: 'fork' }, (err, apps) => { if (err) console.error(`启动worker ${i}失败:`, err); }); } });
然后用PM2启动你的master进程:pm2 start master.js,这样所有worker都会显示在PM2列表里,你的master进程也能正常执行逻辑。
内容的提问来源于stack exchange,提问作者php_nub_qq

