- 后端
- Web框架
【免费下载链接】egg
🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode
导读
@eggjs/cluster(仓库路径 packages/cluster)是 Egg 框架的官方集群管理器,负责把一次startCluster()调用展开为 master / agent / 多 app worker 的完整进程树,并处理 fork、监听、消息路由、健康检查与优雅退出。本文以该包的 CHANGELOG.md 为主线,结合 README.md 与src/下的源码实现,梳理从 1.x 到 4.x 的关键演进(TypeScript 迁移、worker_threads 启动模式、sticky 会话保持、HTTPS 支持等),并给出完整可复用的选项说明与启动示例。读完你将掌握 Egg 多进程模型的核心原理,以及如何在生产环境中正确配置集群启动参数。
一、包定位:Egg 的进程编排层
@eggjs/cluster早期名为egg-cluster,自 3.0.0 起更名为@eggjs/cluster并全面迁移到 TypeScript(见 CHANGELOG.md 中 3.0.0 一节)。它不在 Egg 运行时内部,而是站在应用之外,通过startCluster()一次性拉起所有进程。入口源码 src/index.ts 中给出了清晰启动流程:
[startCluster] -> master -> agent_worker -> new [Agent] -> agentWorkerLoader `-> app_worker -> new [Application] -> appWorkerLoader- master:唯一的控制进程,负责 fork 与管理子进程、端口检测、消息转发、健康检查;
- agent_worker:常驻的 Agent 实例进程,承载与业务无关的后台任务(如日志切割、定时调度协调);
- app_worker:若干 Application 实例进程,真正对外提供 HTTP(S) 服务。
包名的两次代际切换
CHANGELOG 记录了两个关键转折点,理解它们有助于排查升级问题:
| 版本 | 变更 | 影响 |
|---|---|---|
| 3.0.0 | 包名egg-cluster→@eggjs/cluster,改为 ES Module 导出,Node 最低版本提到 18.19.0 | 旧的require('egg-cluster')需要同步迁移,且低版本 Node 不再受支持 |
| 4.0.0+ | 移除 Node.js < 22.18.0 支持,仅支持 egg@4 | 对应 package.json 中"engines": { "node": ">=22.18.0" },属于硬性破坏性变更 |
CHANGELOG 顶部还注明:后续版本的变更记录已统一迁移到 GitHub Releases 页面,仓库内 CHANGELOG 仅保留历史快照,因此下文以源码实现为准来验证各版本累积下来的能力。
二、核心 API:startCluster 与 Master
2.1 最小的启动代码
CommonJS:
const { startCluster } = require('@eggjs/cluster'); startCluster({ baseDir: '/path/to/app', framework: '/path/to/framework', });ESM 与 TypeScript:
import { startCluster } from '@eggjs/cluster'; startCluster({ baseDir: '/path/to/app', framework: '/path/to/framework', });startCluster返回一个 Promise,应用完全启动后 resolve(src/index.ts):
startCluster(options).then(() => { console.log('started'); });2.2 Master 内部做了什么
Master继承自get-ready的ReadyEventEmitter(src/master.ts)。从源码看,构造函数启动后依次执行:
parseOptions()解析并校验全部选项(src/utils/options.ts);- 启用 Node 编译缓存(设置
NODE_COMPILE_CACHE到baseDir/.egg/compile-cache); - 初始化
WorkerManager(worker 登记与健康检查)与Messenger(消息总线); - 读取 framework 的
package.json,打印 Node / 框架版本信息; - 注册
agent-exit、agent-start、app-exit、app-start、reload-worker事件处理器,以及SIGINT/SIGQUIT/SIGTERM信号处理; - 写入
pidFile(若配置); detectPorts()探测 cluster 通信端口与 sticky worker 端口;- 按
startMode选择 process 或 worker_threads 实现,然后forkAgentWorker()——agent 启动成功后才 fork app workers(this.once('agent-start', this.forkAppWorkers.bind(this)))。
这一"先 agent 后 app"的顺序是 Egg 多进程模型的关键:Agent 失败时 master 直接以 code 1 退出,避免出现"应用已启动但依赖的后台进程异常"的半健康状态。
三、完整 Options 说明(README + 源码双重校验)
下表继承自 README.md 的 Options 表,并依据 src/utils/options.ts 补充默认值与约束:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
baseDir | String | process.cwd() | 应用目录,目录下必须存在package.json,否则parseOptions会直接 assert 失败 |
framework | String | 从baseDir/package.json推导 | 框架路径,支持绝对路径或 npm 包名;customEgg已废弃,请改用本项 |
plugins | Object | - | 单元测试用自定义插件 |
workers | Number | os.cpus().length | app worker 数量,也接受数字字符串(内部parseInt) |
sticky | Boolean | false | 是否开启 sticky 会话保持模式 |
port | Number | http 默认7001,https 默认8443 | 监听端口;传null或字符串均可,字符串会被解析为数字 |
debugPort | Number | - | 仅 http 协议监听的调试端口 |
https | Object | - | 启动 HTTPS 服务;key/cert/ca必须是指向文件的完整路径 |
require | Array\|String | - | 注入到 worker / agent 进程的模块,字符串会被自动转成数组 |
pidFile | String | - | master PID 写入的文件路径,退出时自动清理 |
startMode | String | 'process' | 可选'worker_threads',用 worker_threads 启动 app 与 agent worker |
ports | Array | - | 每个 app worker 的启动端口,如[7001, 7002, 7003],仅worker_threads模式生效 |
env | String | process.env.EGG_SERVER_ENV | 自定义运行环境 |
3.1 HTTPS 配置的三种写法
源码 src/utils/options.ts 显示:
- 推荐写法:
https: { key, cert, ca },key/cert 必填,三者均校验文件存在; - 兼容写法:
https: true+ 顶层key/cert(已废弃,会打印 deprecation 警告,最终被归一化为https: { key, cert }); - 旧版顶层
key/cert直接传参的方式(CHANGELOG 1.14.0 引入的 "https options")仍被保留兼容。
证书路径由 src/app_worker.ts 在真正 listen 前读取为 Buffer,再创建createHttpsServer。这解释了为什么文档要求"必须是完整路径"——相对路径会在 worker 进程里解析错位。
3.2 环境变量
| 环境变量 | 含义 |
|---|---|
EGG_APP_CLOSE_TIMEOUT | app worker 优雅退出(关闭)的超时毫秒数 |
EGG_AGENT_CLOSE_TIMEOUT | agent worker 优雅退出的超时毫秒数 |
EGG_MASTER_CLOSE_TIMEOUT | 两者共同的兜底超时,默认'5000' |
关闭顺序与超时读取逻辑见 src/master.ts:先向所有 app worker 发SIGTERM,等待EGG_APP_CLOSE_TIMEOUT;再向 agent worker 发SIGTERM,等待EGG_AGENT_CLOSE_TIMEOUT。整体 15 秒内未完成则以 code 2 强制退出。
四、两种启动模式:process 与 worker_threads
startMode决定 worker 的底层载体,这是 2.0.0 引入的核心能力(CHANGELOG 2.0.0 一节 "feat: support worker_threads (#101)")。
4.1 process 模式(默认)
基于 Node 原生cluster+cfork。实现见 src/utils/mode/impl/process/app.ts:
cfork按workers数量 fork 子进程;refork: this.isProduction——仅生产环境自动拉起崩溃的 worker,本地开发不自动重启;windowsHide: process.platform === 'win32'(对应 1.24.0 的windowsHide支持);- worker 的
exit事件统一转为app-exit消息回传 master,由onAppExit决定是 refork 还是退出(src/master.ts)。
4.2 worker_threads 模式
通过new ThreadWorker()在线程而非独立进程里运行 Application/Agent(src/utils/mode/impl/worker_threads/app.ts),消息走parentPort.postMessage。此模式下可用ports为每个 worker 指定独立端口:
startCluster({ baseDir: '/path/to/app', startMode: 'worker_threads', workers: 3, ports: [7001, 7002, 7003], });与 process 模式的本质区别在于:
- worker 的标识由
threadId承担,而非 PID(get workerId(): number { return this.instance.threadId; }); - 共享同一进程内的资源,启动更轻量;
- 兼容性注意:测试用例 test/worker_threads.test.ts 中明确标注
--import=tsx/esm这类 Node 启动参数在 worker_threads 模式下不受支持,测试被it.skip跳过。
4.3 端口监听差异
process 模式下所有 worker 共享同一port(由 master/cluster 负载均衡),sticky 模式则不同(见下节);worker_threads 模式要求ports数组与workers一一对应,否则回退逻辑由 src/app_worker.ts 处options.port || listenConfig.port兜底。此外 2.1.1 曾修复server.address()返回null时自动补端口的问题(CHANGELOG 2.1.1 一节),说明 worker_threads 模式对端口的解析路径与 process 模式并不完全一致。
五、sticky 模式:会话保持的负载均衡
普通 cluster 模式下,同一客户端的多个请求可能落到不同 worker,导致本地 session 丢失。sticky 模式(1.4.0 引入,CHANGELOG 1.4.0 一节)把"同一 IP 固定路由到同一 worker":
- master 自己 listen 真实端口(src/master.ts),用
pauseOnConnect: true挂起连接; - 根据客户端
remoteAddress的 IP 数字部分对workers取模,选出固定 worker(stickyWorker(ip),src/master.ts); - 通过
sticky-session:connection消息把 socket 转交给对应 worker,worker 端在stickyWorkerPort上监听127.0.0.1并server.emit('connection', connection)接管(src/app_worker.ts)。
实现细节上有两个值得注意的修复历史:
- 1.23.2:三次握手后收到 RST 的 socket 会被直接
connection.destroy(),防止空连接破坏路由; - 1.23.3:sticky 模式下不应在
server.listen()时过早 ready,必须等 master socket server 真正启动(对应 src/master.ts 中startMasterSocketServer回调后才ready(true)的逻辑)。
开启方式:
startCluster({ baseDir: '/path/to/app', sticky: true, });启用后 master 日志会额外打印with STICKY MODE!(src/master.ts)。
六、消息通信:Messenger 拓扑
@eggjs/cluster自己实现了一套跨进程消息总线Messenger(src/utils/messenger.ts),拓扑为:
┌────────┐ │ parent │ /└────────┘\ / | \ / ┌────────┐ \ / │ master │ \ / └────────┘ \ / / \ \ ┌───────┐ ┌───────┐ │ agent │ ------- │ app │ └───────┘ └───────┘- 消息体含
action、data、to、from、receiverWorkerId等字段; - 不指定
to时有默认路由:agent -> app、app -> agent、parent -> master; - 通过
receiverWorkerId可精确投递到指定 worker; - 在应用代码里可用
process.send({ action: 'xxx', data, to: 'agent' })与 master/agent/app 通信(详见 src/utils/messenger.ts 的注释示例)。
master 侧重要的消息动作包括egg-ready(回传给 parent,携带实际port/debugPort/address/protocol)、egg-pids(同步当前存活 worker 列表给 agent/app)与agent-worker-died/app-worker-died等。app worker 启动后还会回传realport,因为真实端口可能由app.config.cluster.listen覆盖(src/master.ts)。
七、健康检查与容错机制
7.1 WorkerManager 巡检
生产环境下(env非 local/unittest 或NODE_ENV === 'production'),master 会启动周期巡检(src/utils/worker_manager.ts):
- 每 10 秒检查一次 agent 与 worker 是否都存活;
- 若某次检查异常计数
exception++,连续 3 次异常则触发exception事件; - master 收到后抛出
ClusterWorkerExceptionError并process.exit(1)(src/master.ts)。
7.2 退出与重启策略
- agent 退出:启动期间失败 → master 以 code 1 退出;运行期间崩溃 → 1 秒后自动重新 fork(
onAgentExit,src/master.ts); - app worker 退出:启动期间失败 → master 以 code 1 退出;运行期间崩溃 → 生产环境由
cfork自动 refork(src/utils/mode/impl/process/app.ts); - 调试模式:若以
--inspect/--debug启动且 worker 被SIGKILL(常见于调试器强制终止),master 判定为调试器所为,10ms 后整体退出(onAppExit中this.options.isDebug && signal === 'SIGKILL'分支); - 启动超时:app worker 监听
startTimeout事件,超时以 code 1 退出(src/app_worker.ts)。
7.3 优雅关闭
收到SIGINT/SIGQUIT/SIGTERM后,master 依次 kill app workers 与 agent worker,并保证agent 在 master 之前退出(kill 顺序见 src/master.ts 注释 "make sure Agent Worker exit before master exit")。2.2.1 曾修复关闭时 worker 收不到SIGTERM的问题,1.9.1 也曾加入 100ms 延迟确保信号送达,这些都是为了优雅关闭的可靠性所做的修补。
八、版本演进中的关键修复清单(从 CHANGELOG 提炼)
| 版本 | 类型 | 内容 | 对应源码痕迹 |
|---|---|---|---|
| 4.0.0+ | 破坏性 | 移除 Node < 22.18.0,仅支持 egg@4 | package.jsonengines |
| 3.0.1 | 修复 | require 支持 paths(模块注入可解析) | parseOptions/importModule的paths: [options.baseDir] |
| 3.0.0 | 特性 | tshy 同时产出 CJS 与 ESM,TypeScript 化 | package.jsonexports指向src/index.ts |
| 2.4.0 | 特性 | 升级 detect-port v2 | detectPorts()动态探测端口 |
| 2.2.0 | 特性 | 新增debugPort,可同时监听 http 与 https | src/app_worker.ts 双 server |
| 2.1.0 | 特性 | options.env自定义运行环境 | isProduction判定优先读options.env |
| 2.0.0 | 特性 | worker_threads 支持 | src/utils/mode/impl/worker_threads/ |
| 1.27.0 | 特性 | agent/app 启动错误格式化为FrameworkError | agent_worker的startErrorHandler |
| 1.26.0 | 特性 | 启动时打印process.env.HOST | getAddress()中读取process.env.HOST |
| 1.25.0 | 特性 | 支持config.cluster.https | app worker 合并clusterConfig.https与options.https |
| 1.24.0 | 特性 | windowsHide支持 | cfork({ windowsHide }) |
| 1.23.0 | 特性 | 保存 pid 文件 | masterpidFile写入/清理 |
| 1.22.0 | 特性 | 退出时 kill 全部子进程 | _doClose的关闭链 |
| 1.21.0 | 特性 | 启动失败时优雅退出 | 各 workerstart error, exiting with code:1分支 |
| 1.13.0 | 特性 | 新增 worker manager 并检查 worker/agent 状态 | WorkerManager.startCheck() |
| 1.12.3 | 修复 | EADDRINUSE时 master 应退出 | app workerserver.once('error')→exitProcess() |
| 1.12.2 | 修复 | 禁用 worker 自动 refork | disableRefork/refork: this.isProduction |
| 1.6.4 | 修复 | master 被SIGKILL时 agent 也应退出 | agent 的disconnect事件处理 |
这些修复条目并非孤立补丁,而是逐步沉淀出当前 master / worker 状态机(state: 'listening'、disableRefork、isAllWorkerStarted等字段)的演进痕迹,阅读 src/utils/worker_manager.ts 与 src/master.ts 可以看到它们的最终形态。
九、从零启动一个生产集群
综合上文,一个接近生产配置的启动脚本如下:
const path = require('node:path'); const { startCluster } = require('@eggjs/cluster'); startCluster({ baseDir: path.join(__dirname, 'app'), // framework: 'egg', // 或指向本地框架的绝对路径 workers: process.env.WORKERS ? Number(process.env.WORKERS) : require('node:os').cpus().length, port: 7001, sticky: false, // 有本地会话依赖时开启 pidFile: path.join(__dirname, 'run/master.pid'), env: process.env.EGG_SERVER_ENV, // startMode: 'worker_threads', // 需要更轻量的启动时启用,并配合 ports // ports: [7001, 7002, 7003], // require: ['tsx/register'], // 注入需要的模块 }).then(() => { console.log('egg cluster started'); });部署注意事项:
- Node 版本:4.x 要求 Node >= 22.18.0(package.json);
- HTTPS:证书必须为文件绝对路径,
https: { key, cert, ca }优先; - 关闭超时:通过
EGG_MASTER_CLOSE_TIMEOUT/EGG_APP_CLOSE_TIMEOUT/EGG_AGENT_CLOSE_TIMEOUT调节优雅退出窗口,默认 5000ms,master 整体兜底 15s; - 健康检查:生产环境会自动每 10s 巡检,连续 3 次异常即整体退出,配合进程守护工具(如 systemd / docker restart)即可实现故障自愈。
十、总结
@eggjs/cluster从 2016 年的egg-cluster一路演进至今,其 CHANGELOG 本身就是一部"Node.js 集群进程管理最佳实践"的浓缩史:从 0.x 的master 不加载配置、到 1.x 的 sticky/https/pidFile/健康检查、再到 2.x 的 worker_threads 轻量启动与 3.x 的 TypeScript + 双模块格式、最终在 4.x 收敛到 Node 22+ 与 egg@4 单一目标。对使用者而言,掌握本文的 Options 语义、两种 startMode 的取舍、sticky 的适用场景以及 master 的容错与优雅退出策略,就足以在生产环境中稳定地编排 Egg 的多进程应用。
如需深入源码,推荐按以下路径阅读:
- 启动入口与整体流程:src/index.ts、src/master.ts
- 选项解析与默认值:src/utils/options.ts
- worker 进程实体:src/app_worker.ts、src/agent_worker.ts
- 消息通信与健康检查:src/utils/messenger.ts、src/utils/worker_manager.ts
- 两种启动模式的实现对照:src/utils/mode/impl/process、src/utils/mode/impl/worker_threads
- 测试样例:test/worker_threads.test.ts、test/options.test.ts
- 后端
- Web框架
【免费下载链接】egg
🥚🥚🥚🥚 Born to build better enterprise frameworks and apps with Node.js & Koa. https://307.run/eggcode
相关推荐
OpCore Simplify终极指南:5分钟完成黑苹果OpenCore EFI自动化配置
OpCore Simplify终极指南:5分钟完成黑苹果OpenCore EFI自动化配置 OpCore Simplify是一款革命性的OpenCore EFI
后端Web框架微信聊天记录导出完整教程:WeChatMsg 三步备份为 HTML / Word / CSV
微信聊天记录导出完整教程:WeChatMsg 三步备份为 HTML / Word / CSV 换手机、淘汰旧设备、项目交接,或者只是想翻回去年某段对话——这些时
后端Web框架WinUtil 完整指南:Windows 11 装软件、提速、修故障,一个界面 3 分钟搞定
WinUtil 完整指南:Windows 11 装软件、提速、修故障,一个界面 3 分钟搞定 新装的 Windows 11 要装十几个软件?老电脑慢到想摔键盘?
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考