
如何在 PM2 下配置 Socket.IO 集群instances、cluster 模式与优雅关闭【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io本文解决一个部署场景的问题用 PM2 的 cluster 模式让 Socket.IO 服务跑在多个 Node.js 进程中使所有 worker 之间能互相广播并在收到停止信号时优雅关闭连接而不是被直接杀掉。socket.io 仓库自带 examples/pm2-example 目录完整给出了 ecosystem 配置、worker 入口脚本、前端验证页面和一套socket.io/pm2管理命令下文按该示例逐段说明。准备条件与依赖pm2-example的 package.json 声明了 ESMtype: module和以下依赖版本可作为自己项目的依赖清单参照{ type: module, dependencies: { socket.io/cluster-adapter: ^0.3.0, socket.io/sticky: ^1.0.4, socket.io: ^4.8.3 } }其中两个关键包的作用socket.io/cluster-adapter在多个 Socket.IO server 之间转发广播包。根据其 README它可与socket.io/sticky配合使用在同一个 Node.js cluster 的各 worker 之间广播。支持的成员功能包括 broadcasting 和 utility methodssocketsJoin、socketsLeave、disconnectSockets、fetchSockets、serverSideEmit。socket.io/sticky实现 sticky session。socket.io 的 Readme 说明断线检测依赖心跳定时器pingInterval/pingTimeout这些定时器要求客户端后续请求被导向同一台服务器因此多节点部署存在sticky-session要求——这是 cluster 模式下必须调用setupWorker的原因。Fastify 变体入口还会用到fastify ^5.8.5与fastify/static ^9.1.3。配置 ecosystem.config.jsinstances 与 cluster 模式PM2 侧的核心配置是 ecosystem.config.js内容很短export const apps [ { name: pm2-example, script: entrypoint.js, // script: fastify-entrypoint.js, instances: max, exec_mode: cluster, }, ];各项含义namePM2 进程名后面的 stop/ps/cleanup 命令都用它定位进程可按项目改名但后续命令要同步改。scriptPM2 fork 后执行的入口脚本。示例默认是entrypoint.js原生 http server 变体注释里那行是换成fastify-entrypoint.js的开关两者二选一不要同时启用。instances: max按 CPU 核数启动最大数量的进程实例。exec_mode: cluster以 Node.js cluster 模式运行各实例sticky 的会话保持才在这组 worker 之间生效。注意该文件用export const导出ESM与package.json的type: module保持一致如果用 CommonJS 项目需要相应写成module.exports形式。编写 worker 入口adapter 与 sticky以示例的 entrypoint.js 为主路径它用原生 http server 同时托管页面和 Socket.IOimport { readFileSync } from node:fs; import { createServer } from node:http; import { Server } from socket.io; import { createAdapter } from socket.io/cluster-adapter; import { setupWorker } from socket.io/sticky; const httpServer createServer((req, res) { if (req.method GET req.url /) { const content readFileSync(./index.html); res.writeHead(200, { content-type: text/html, }); res.write(content); res.end(); } else { res.writeHead(404).end(); } }); const io new Server(httpServer, { adapter: createAdapter(), }); setupWorker(io); io.on(connection, (socket) { console.log(connect ${socket.id}); socket.emit(nodeId, process.env.NODE_APP_INSTANCE); socket.conn.on(upgrade, (transport) { console.log(transport upgraded to ${transport.name}); }); socket.on(disconnect, (reason) { console.log(disconnect ${socket.id} due to ${reason}); }); });三个必须保留的调用new Server(httpServer, { adapter: createAdapter() })——装上 cluster adapter 后本进程发出的广播才能被其他 worker 收到。setupWorker(io)——向 master 进程注册本 worker 并建立 sticky 所需的内部连接。process.env.NODE_APP_INSTANCE——cluster 模式及 PM2会给每个实例注入该环境变量作为实例编号示例用它向客户端回发nodeId事件是后面验证连接确实落在不同实例上的依据。如果你的 HTTP 层用 Fastify可参考 fastify-entrypoint.js结构相同createAdapter()setupWorker(io)差别在静态文件交给fastify/static且关闭逻辑走 Fastify 的生命周期钩子见后文。启动与查看实例示例的 package.json 用socket.io/pm2包装了常用操作scripts: { start: npx socket.io/pm2 startOrReload ecosystem.config.js, stop: npx socket.io/pm2 stop pm2-example, ps: npx socket.io/pm2 ps, cleanup: npx socket.io/pm2 delete pm2-example }在目录里执行npm install装好依赖后npm run startstartOrReload表示如果名为pm2-example的应用已存在则先 reload 再启动。随后用下面的命令查看各实例的进程编号与状态npm run ps验证集群是否生效验证页就是示例自带的 index.html由 server 本身在/路径返回。浏览器打开http://localhost:端口/后页面显示三个字段Connection status连接成功时变为connected断线时变为disconnectedNode ID服务端通过socket.emit(nodeId, process.env.NODE_APP_INSTANCE)下发的实例编号Transport当前传输方式并通过socket.io.engine.on(upgrade)在升级时刷新。客户端连接代码节选自 index.htmlconst socket io({ transports: [polling, websocket], }); socket.on(connect, () { $transport.innerText socket.io.engine.transport.name; socket.io.engine.on(upgrade, (transport) { $transport.innerText socket.io.engine.transport.name; }); });服务端同时会在控制台输出entrypoint.js中的日志语句connect socket.id transport upgraded to websocket disconnect socket.id due to reason判断方法页面显示connected且 Node ID 为0到instances数量减一之间的某个数字说明连接落在了对应 worker重新加载几次页面Node ID 出现变化说明 sticky 会话在多实例间正常分配。若只看到一个固定编号且与预期实例数不符先npm run ps确认实例数量是否符合instances: max的预期。优雅关闭示例在进程收到SIGINT时调用io.close()等 Socket.IO 完成清理后再退出// graceful shutdown process.on(SIGINT, () { io.close((err) { process.exit(err ? 1 : 0); }); });Fastify 变体多一步主动断开本实例连接的逻辑在preClose钩子里调用io.local.disconnectSockets(true)关闭该 server 上所有活跃连接关闭入口换成fastify.close()fastify.addHook(preClose, (done) { // close all active connections on this server io.local.disconnectSockets(true); done(); }); // graceful shutdown process.on(SIGINT, () { fastify.close((err) { process.exit(err ? 1 : 0); }); });两种写法对应不同取舍http server 变体只关闭 Socket.IO 自身Fastify 变体会在关闭前显式断开本地所有 socket适合希望停止时不遗留挂起连接的部署。停止整个应用的命令是npm run stop彻底删除 PM2 应用记录用npm run cleanup后者会执行delete pm2-example之后该应用不在 PM2 列表里需重新start才能拉起。限制与边界cluster adapter 解决的是同机 cluster 的各 worker 之间的广播socket.io/cluster-adapterREADME 也说明它是与socket.io/sticky搭配、针对同一 Node.js cluster 的方案。跨机器部署属于另一类方案README 中另列了 Postgres/Redis/MongoDB adapter 等外部项目不在本文范围内。心跳断线检测要求客户端后续请求被导向同一服务器所以 cluster 模式下setupWorker(io)不可省略去掉它后跨实例的心跳行为没有保证。示例入口以 ESM 编写import语法、type: module直接复制到自己的 CommonJS 项目时需要整体转为require形式adapter/sticky 的调用方式不变。【免费下载链接】socket.ioBidirectional and low-latency communication for every platform项目地址: https://gitcode.com/gh_mirrors/so/socket.io创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考