Cluster 多进程
VextJS 通过一个 Master 管理多个 HTTP Worker。每个 Worker 有独立应用实例,监听同一服务端口;Master 负责启动、故障替换、心跳检查和滚动重启。
先完成双 Worker 启动与请求验证,再调整数量和恢复策略。开发热重载见热重载,生产构建前置步骤见构建。
快速开始
通过配置启用
使用已安装依赖的 TypeScript API 项目,例如 CLI 页的脚手架。在现有生产配置对象中合并以下项,保留业务配置:
新增一个仅供验证的路由:
停止同项目的 dev 服务后,在项目根执行:
应看到两个 Worker 各自 ready,最终汇总 workers=2/2。首个 Worker 启动失败会终止启动;后续 Worker 失败可能以不足目标数量的状态继续,所以需检查实际 ready 数。
启动效果与请求验证
另开终端,多次建立新请求:
请求应为 HTTP 200,data.pid / data.workerId 来自实际处理该请求的 Worker。结合详细启动日志确认两个 Worker 就绪;连接调度不保证两次请求一定轮流命中不同 Worker,因此不能只用两次返回值判断容量。
检查项目根生成的 .vext.pid:它记录 Master PID,不是 HTTP Worker PID。status 的能力边界见下文。验证后通过前台 Ctrl+C 关闭,Unix/macOS 也可在另一终端执行 npx vextjs stop,并核实进程、端口和 PID 文件状态。示例诊断路由按项目需要保留或移除。
通过环境变量启用
VEXT_CLUSTER=1 也能启用 Cluster,Worker 数量仍由配置控制。它会覆盖未启用的配置;设置为 0 不会反向禁用已配置的 cluster.enabled: true。
Master 先加载配置并进行端口预检,将本次 bootstrap config provider 的 patch 传给 Worker 复用。每个 Worker 仍执行自己的应用初始化;不要据此把插件副作用当作只执行一次。
架构概览
- Master 进程:不处理 HTTP 请求,负责管理 Worker 进程的生命周期
- Worker 进程:每个 Worker 运行一个完整的 VextJS 应用实例,独立处理 HTTP 请求
- IPC 通信:Master 和 Worker 之间通过 Node.js 内置的进程间通信(IPC)交换消息
配置选项
在 src/config/production.ts 等选定 profile 中配置 Cluster。以下列出当前默认参数,并显式开启 Cluster;一般只需覆盖需要修改的字段:
Worker 数量策略
请使用有效正整数,不依赖越界值兜底。CPU 检测先尝试 os.availableParallelism(),缺失、抛错或返回无效值时,才以 os.cpus() 为基础尝试 Linux cgroup v1 配额。有效配额会约束检测值;文件不存在、无法读取 cgroup 限制、内容无效或未设置有限配额时,回退值可能来自主机 CPU 数。不要认为自动值必然精确对应所有容器 CPU quota。
容器中应先用本页的 worker-info 路由核对实际进程数量,再根据 CPU、内存和连接预算配置显式 workers: 2 等值。"auto-1" 只是将检测值减一,不能修复配额读取失败。
每增加一个 Worker 都会增加应用实例、数据库连接池、缓存和堆内存占用。根据请求负载、内存和外部连接限额确定数量,并以实际压测验证,不能仅按 CPU 倍数保证吞吐。
状态与多进程边界
Worker 之间不共享普通变量、Service 实例或内存 Store。需要全局一致的数据,应选择具有共享语义的存储;例如内存限流是各 Worker 独立计数,不能视为全局配额,见限流。Session 与缓存的共享方式同样需按对应 Store 设计。
源 IP 亲和性
需要让同一 TCP 源 IP 的多条独立连接进入同一 Worker 时,设置 cluster.sticky: "ip"。Master 接收并暂停新连接,按源地址和稳定逻辑槽位计算路由,再通过 IPC 把 socket 交给 Worker;业务数据由 Worker 直接处理。五种内置 Adapter 的字符串配置与官方工厂均支持此模式。
默认 "none" 继续使用普通 Cluster 监听路径:Linux 使用 Node SCHED_NONE,其他平台使用 SCHED_RR。"ip" 直接实现真实亲和性,替换旧的 RR 占位行为;无需新增配置或迁移开关。仅在 Cluster 启动时生效,dev/testing 和普通单进程启动仍沿用原路径。
路由依据是 TCP 对端地址,不读取 X-Forwarded-For、Forwarded、cookie 或 SID;应用的 trustProxy 与 req.ip 解析不能改变已完成的连接分发。反向代理或 NAT 会把多个客户端合并为同一个源 IP,可能集中到一个 Worker。外部负载均衡通常只能选择实例或独立上游,不能自动选择共享端口中的 Worker。
槽位和可用集合稳定时,同一 IP 的新连接选择同一槽位。崩溃时仅不可用槽位的流量回退,恢复后可能回到原槽位。滚动替换保留槽位编号,但替换进程后内存状态不会保留;旧连接仍由旧 Worker 完成或在关闭预算耗尽时断开。改变 sticky 或 workers 配置需要完整重启 Master,rolling reload 会拒绝与 Master 策略不一致的候选。
Worker 自行收到 SIGTERM/SIGINT 或因致命错误进入应用关闭流程时,会通知 Master 撤销新连接路由资格;新连接可选择其余可用槽位,在途连接继续使用原关闭预算。自行退出后仍按 autoRestart 与重启预算恢复原槽位,Master 主动替换或整体停止不会因此重复启动 Worker。
交接设有有界额度和超时:当前全局最多 1,024 条待交接连接、每个 Worker 最多 64 条,交接期限为 5 秒;这些内部防护值不是 HTTP 请求执行时限。目标槽位额度耗尽时关闭新连接,不把它改投到其他繁忙程度较低的 Worker;超时且无法确认交接的进程会被终止并进入现有 auto-restart 规则。框架不会重放请求,客户端重试需考虑幂等性。指标通过 runtime snapshot 的 summary.connections 记录,按 Worker 指标周期及生命周期事件更新,包含 pending、committed、rejected、timedOut、sendFailed 与 backpressure;committed 表示提交指令发送完成,不表示 HTTP 请求成功。
IP 亲和性适合依赖进程内状态的多连接应用,但不提供 Session 高可用,也不自动启用 Socket.IO、TLS 或 HTTP/2。状态一致性及故障恢复仍建议使用共享存储。自定义 Adapter 必须显式支持 socket handoff,参见适配器。
CLI 命令
VextJS CLI 提供了完整的 Cluster 管理命令:
vext start — 启动
如果配置中 cluster.enabled: true 或设置了 VEXT_CLUSTER=1,vext start 会自动以 Cluster 模式启动。
vext stop — 停止
命令读取 PID 文件并发送 SIGTERM,最多等待 Master 退出 30 秒。正常 Master 关闭流程通知 Worker 停止接收请求、等待处理和清理、退出后清理 PID 文件。超时返回非零不代表进程已经停止。
Windows 的外部进程终止不等同于 Unix 信号驱动的完整清理。通过前台 vext start 的 Ctrl+C,CLI 可经父子 IPC 请求关闭;独立 vext stop 或操作系统强制终止不能保证 onClose 执行。具体超时层次见优雅关闭。
vext reload — 滚动重启
CLI 向 Master 发送 SIGHUP 后即返回;发送成功不等于所有 Worker 已替换完成。Master 对启动时记录的旧 Worker 逐个执行:
- 启动新 Worker,等待 ready。
- 新 Worker ready 后,通知对应旧 Worker 关闭。
- 等待旧 Worker 退出;超时则强制终止。
- 按 workerDelay 等待,再处理下一个。
新 Worker 启动失败时保留旧 Worker,记录失败并继续其他替换。检查日志中的 replaced/total 以及实际请求,不能把出现 complete 字样当作全部成功。长连接、关闭超时、应用错误或资源不足仍可能导致中断;滚动策略不提供任意场景的零停机保证。
滚动替换不重新创建 Master。Worker 数量、Master 心跳/退避配置以及启动时取得的 provider patch 等不会因发送信号就整体刷新;修改这些设置应重新启动完整服务,并按部署流程切换流量。
Windows 不支持当前 vext reload 的信号操作,命令会失败。TypeScript 应先成功构建可用产物,再执行部署更新。省略 cluster.reload 不会禁用滚动重启,框架仍使用默认等待参数。代码、配置和产物的更新方式需保证旧、新 Worker 都能读取一致版本。
vext status — 查看状态
正常可显示 Master PID 和 PID 文件路径;随后尝试 http://<host>:<port>/health,默认 host 为 127.0.0.1、port 为 3000:
只有健康响应顶层包含 pid / uptime / memory 时,才追加对应详情。它不会解包 Vext 常规响应的 data,不会读取应用配置中的端口,也不会扫描全部 Worker。/health 需由应用提供;位于 /api/health 等其他路径时不适用这个固定探测。
例如,目标健康接口直接返回上述顶层字段时,输出可以为:
这些数值仅示意一次健康请求返回的进程信息;本页默认包装响应的示例不会自动产生这些详情,也不能将它作为全部 Worker 的统计。
status 对 not running、stale、不可达等查询结果也可退出 0,不应直接作为部署健康门禁。使用 --host、--port、--pid-file 指向实际实例,并独立检查业务健康响应。
自动故障恢复
Worker 崩溃重启
autoRestart: true 时,非主动关闭的 Worker 退出通常触发替换;ready 前的候选失败由对应启动或替换流程处理。新 Worker 有新的编号/PID,不是让原进程原地恢复。
常见日志形式如下,数值为示意:
Worker 内存、未完成请求及未持久化状态不会随进程自动恢复。
指数退避
连续崩溃时,重启延迟逐步增加(指数退避),避免频繁重启消耗系统资源:
崩溃循环保护
restartWindow 默认 60,000 毫秒,maxRestarts 默认 5。计数由整个 Master 共用,多个 Worker 的异常退出共同消耗预算;窗口内第 6 次触发时暂停该次自动重启。
时间窗口过期不会自动启动一个补齐容量的定时任务。仍有健康 Worker 时,Master 保持运行并等待运维恢复容量。全部 Worker 消失且没有待执行重启、启动候选或 reload 工作时,标准宿主记录 fatal-capacity-loss、清理自身 PID 并以 1 退出;CLI 转发失败状态。外部监督应配置重启退避,先排查崩溃根因。正常 stop 仍以 0 退出;底层 ClusterMaster 的事件消费者自行决定退出策略。
心跳检测
Worker 默认每 10 秒自发发送 heartbeat。Master 在 healthCheck.enabled: true 时,每隔 interval(默认 15 秒)检查 ready Worker 的最后心跳时间;超过 timeout(默认 30 秒)会强制终止对应 Worker,后续是否替换还受 autoRestart 与重启预算控制。
这不是向 HTTP /health 发请求,也不是每 15 秒主动发送 IPC health-check。由于按间隔检查,检测时间不保证恰好在第 30 秒发生。
内存阈值
Worker 每 60 秒检查一次 heapUsed,默认阈值 1 GiB,可用 cluster.memoryThreshold(字节)调整。超限时该 Worker 仅发送一次 request-restart,请求 Master 先启动替代 Worker;它不会立即退出,也不是 RSS/容器总内存硬限制。
替换请求失败不能视为已经释放内存,应观察日志和实际进程。
PID 文件
Cluster 模式启动时,Master 进程会写入 PID 文件(默认 .vext.pid),用于 vext stop / vext reload / vext status 命令定位进程。
PID 文件在以下时机自动管理:
- 创建:Master 启动时
- 删除:Master 正常退出时
- 检测:启动时检测是否已有运行中的 Cluster
相对路径以启动工作目录解析。stop/reload/status 不会自动从应用配置读取该路径,请传相同的 --pid-file。异常强制终止可能留下旧文件;先核实其中 PID 对应的实际进程,避免以删除 PID 文件代替停止服务。
将 .vext.pid 添加到 .gitignore,避免提交到版本控制。
与优雅关闭的配合
Cluster 模式下的优雅关闭流程:
超时有独立的两层,注意单位:
Master 等待到期可能 SIGKILL Worker,应用清理不能保证继续执行。为内部清理留足外层时间,例如:
关闭顺序更完整的说明见 Hooks。强制终止或超时不能作为清理钩子已完成的证据。
按环境配置
开发环境推荐使用 vext dev(热重载模式)而非 Cluster 模式。Cluster 主要用于生产环境的多核利用和高可用。
进程间通信
Master 和 Worker 之间通过内部 IPC 协议通信。下表用于理解运行机制,不是业务插件可以假定稳定的包根公共 API;应用不应手工发送 ready 来绕过真实初始化。
由 vext start 启动时,Windows CLI 会通过父子 IPC 向 Master 发送关闭请求;Master 将其交给同一优雅关闭流程,通知 Worker、等待退出并清理 PID 文件。操作系统外部直接终止进程与此流程不同,不保证执行关闭钩子。
下表中的消息类型是 IPC payload 中 type 字段的精确字符串字面量,不包含方向前缀。
Worker → Master 消息
Master → Worker 消息
这些消息由框架维护。请求指标占位不等于服务没有流量;生产监控应基于已接入的实际请求观测。
排查内部进程通信时,消息形态例如:
这里的 PID、workerId 为示意值,timeout 单位为毫秒。应用管理 Worker 请使用本页的 CLI 与生命周期入口,不要从路由中手动发送内部消息。
与 Docker 部署
Dockerfile 示例
以下运行镜像示例要求 TypeScript API 项目已在构建阶段成功生成 dist、start 所需配置位于产物中,且没有额外运行资源。完整多阶段构建见构建。JavaScript source 模式还需携带 src。
建议
- 保留 dist 内身份文件与生产依赖;自定义前端目录、工作区包和外部资源需另行携带。
- Worker 数量以容器实际 CPU、内存及连接配额验证,必要时使用显式数字。
- PID 路径需可写并且每实例独立。
- 容器停止宽限期应大于 Master 等待与应用清理所需时间。
- 若使用外层进程管理器或多个容器副本,要计算总 Worker 数,避免两层自动扩容意外叠加。
Jobs 与 Cluster
存在启用的定时任务时,HTTP Cluster Worker 在应用就绪后注册定时器,并通过 config.jobs.redis 共同协调触发。缺少 Redis 配置会在启动阶段报错;空任务目录或全部关闭的任务无需 Redis。namespace 自动按包名、profile 和运行模式生成,相同副本无需手填;所有副本使用相同 Redis 目标及调度定义。同一触发点只接受一次、同任务重叠跳过,但业务副作用仍需幂等保护。详见定时任务 Jobs。
常见问题
Cluster 模式下 WebSocket / SSE 需要注意什么?
已建立的长连接由持有它的 Worker 处理,Worker 退出时仍可能断开。重连、跨请求状态、消息广播和关闭超时需单独设计;sticky: "ip" 可在槽位与可用集合稳定时保持源 IP 的重连亲和性;Worker 替换、故障或源地址变化后不能保证原进程或内存状态仍在。是否支持具体协议还取决于适配器与应用实现。
Worker 数量设多少合适?
先从可控数量验证,再依据 CPU、内存、响应延迟和数据库连接总量调整。每 Worker 都会初始化应用和连接池;"auto-1" 只是减少进程数,不为 Master 保留或绑定某个物理核心。
如何监控各 Worker 的状态?
使用 vext status 查看 Master PID、PID 文件状态,以及 /health 可达时的单个健康端点详情。当前命令不会输出 worker 表或请求数;生产环境建议配合 Prometheus 或其他监控工具收集更详细的多 Worker 指标。
与 PM2 有何区别?
Vext 内置 Master 负责本框架 Worker 的生命周期。若再由外部进程管理器托管,应明确它管理的是一个 Master 还是多个独立应用实例,并避免双层 Cluster 造成 Worker 数、PID 文件和关闭流程冲突。外层仍需负责 Master 自身退出后的恢复策略。