You need to enable JavaScript to run this app. Node.js集群模式下请求未分发至各Worker进程的异常排查
优惠活动
大模型
产品
解决方案
定价
更多

Node.js集群模式下请求未分发至各Worker进程的异常排查

Node.js 集群模式请求分发异常排查

测试场景与观测现象

原生cluster模块测试

测试使用的核心代码如下:

const http = require("http");
const cluster = require("cluster");
const CPUs = require("os").cpus();
const numCPUs = CPUs.length;

if (cluster.isMaster) {
  console.log("This is the master process: ", process.pid);
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on("exit", (worker) => {
    console.log(`worker process ${process.pid} has died`);
    console.log(`Only ${Object.keys(cluster.workers).length} remaining...`);
  });
} else {
  http
    .createServer((req, res) => {
      res.end(`process: ${process.pid}`);
      if (req.url === "/kill") {
        process.exit();
      }
      console.log(`serving from ${process.pid}`);
    })
    .listen(3000);
}

基于上述代码开展压测观测到的异常:

  • 压测全程所有请求日志均来自同一个固定pid的worker进程,无轮询分发效果
  • 手动访问/kill接口杀掉当前承接请求的worker后,短时间内请求依然关联旧的进程处理逻辑,直到存活worker数量持续减少才会切换到其他pid
  • 代码本身存在日志打印bug:worker退出回调中错误使用全局process.pid(即master进程pid)打印退出信息,没有取实际退出的worker实例pid,容易造成日志误读

PM2集群模式对照测试

  • 执行pm2 start app.js -i 3启动3个集群实例,PM2进程列表显示3个实例全部为online状态
  • 执行loadtest -n 50000 http://localhost:3000压测期间,通过pm2 monit观测到:仅id为0的实例存在CPU、内存占用波动,其余两个online实例无任何请求命中,也没有输出应用访问日志
  • 按照社区建议调整PM2配置重新测试时,出现服务完全无法启动的异常,不同配置的测试过程留存了操作录屏用于效果对比

核心问题成因

  • TCP连接绑定特性:Node.js集群层的请求分发仅在新建TCP连接的节点完成——master进程收到新的连接请求时,才会按照内置调度策略将连接分配给对应worker。一旦客户端和服务端的长连接(默认开启keep-alive复用)建立完成,这个连接上的所有后续请求都会被一直绑定到最初分配的worker上,不会因累计请求数触发重新分配。loadtest工具默认复用已建立的长连接发送全部压测请求,自然所有请求都会落在同一个worker上。
  • 跨平台调度策略差异:Linux环境下Node.js cluster默认采用轮询调度(Round-Robin),新建连接会均匀分发给各个worker;Windows环境下受系统网络接口实现限制,默认的连接绑定逻辑会优先把新连接分配给最近空闲的节点,在单客户端持续压测的场景下,很容易出现连接长时间绑定同一个worker的情况。
  • Worker被杀后的延迟现象:当通过/kill退出当前绑定的worker后,已有的长连接会进入半关闭状态,客户端TCP栈不会立刻感知到连接失效,会在超时重传阶段继续尝试发送请求,直到系统层判定连接断开、重新建立新连接时,master才会将新连接分配给其他存活worker,这就是杀掉worker后短时间仍命中旧pid的核心原因。

正确验证请求分发的调整方案

  • 压测时关闭客户端的连接复用,使用loadtest的-k false参数强制每个请求都新建独立TCP连接,此时master进程才会按照调度策略把每个新连接轮询分配给不同worker
  • 修正退出日志的打印逻辑,改为输出worker.process.pid,避免混淆master进程和worker进程的pid
  • 排查本机是否存在本地端口转发、系统代理等拦截了localhost请求,导致所有请求只绑定到同一个socket节点

内容的提问来源于stack exchange,提问作者falamiw

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.08 09:06:05