Node.js 的 Cluster 模块 vs PM2:生产环境进程管理的选型指南

本文深入对比 Node.js 内置 Cluster 模块与 PM2 在生产环境中的进程管理选型,从多进程原理、负载均衡、自动重启、日志监控和容器部署场景出发,分析两种方案的边界与坑点,帮助不同规模的团队做出务实选择。

单机多核,先用起来再说

很多 Node.js 项目第一次意识到进程管理,是在压测阶段。

AI technology illustration

明明服务器配了 8 核,但 Node.js 进程的 CPU 占用就是上不去,一查,只有其中一个核在忙。Node.js 的主线程是单线程的,这个问题不会因为内存变大而消失。要想利用多核,要么把服务拆成多实例,要么在同一台机器上拉起多个 Node.js 进程,让它们共享对外端口。

这时候,Node.js Cluster 模块PM2 会同时出现在搜索结果里。两个看起来都能做多进程,但选哪个,很多团队其实没想清楚。

先搞清楚 Cluster 模块到底做了什么

Cluster 模块是 Node.js 内置的,它让你可以创建多个工作进程(worker),由主进程(master)统一管理。worker 之间通过 IPC 通信,master 负责对外接收连接,再分发给空闲的 worker。严格说,它的核心能力不是实现负载均衡,而是让多个进程能够共享端口。

一个最基础的示例是这样的:

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const cpus = os.cpus().length;
  for (let i = 0; i < cpus; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} 退出`);
  });
} else {
  require('./app.js');
}

这段代码在 master 里按 CPU 核数 fork 出 worker,每个 worker 都加载同一个 app.js。因为 worker 之间共享服务器句柄,所以多个 worker 可以同时监听同一个端口,由内核或主进程实现连接分发。在大多数平台上,Node.js 默认使用 round-robin 调度,对 HTTP 短连接的表现比较稳定。

但工程上会立刻遇到几个问题:如果 worker 崩溃退出,master 要不要拉一个新的?拉起来后旧请求如何处理?日志分散在这么多进程中,怎么统一查看?worker 之间的内存状态并不共享,某些依赖进程内缓存的服务,可能会拿到不一致的数据。Cluster 模块只是一个底层工具,它把问题从“怎么让 Node.js 多进程跑起来”推进到了“怎么把这些进程管好”。

另外,不要把 Cluster 模块和 worker_threads 搞混。Cluster 是进程级的多实例,每个 worker 都有独立的 V8 堆和事件循环;worker_threads 则是同一个进程内的多线程,共享内存,更适合 CPU 密集型任务。两者解决的问题并不一样。

  • 误区一:Cluster 模块会自动保证 worker 随时可用?不会,它只提供 fork 和事件通知,重启策略要自己写。
  • 误区二:所有 worker 都是无状态的?实际上任何第三方连接都各自独立,需要自己处理状态同步。
  • 误区三:进程数必须等于 CPU 核数?这只是默认参考值,还得看内存和文件句柄。

PM2 的价值不是“多进程”,是“进程管理”

PM2 在很多项目里被当成“启动器”或“守护进程”,其实它最重要的能力,是把进程生命周期管理做成了标准化流程。PM2 的 cluster 模式在底层同样依赖 Node.js 的 cluster 机制,但它在这个基础上补齐了开发者在生产环境真正需要的东西:worker 崩溃自动重启、日志聚合、默认监控、开机自启、优雅停机和零停机重启。

用一个 ecosystem.config.js 文件就能把服务跑起来:

module.exports = {
  apps: [{
    name: 'api-service',
    script: './src/server.js',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production'
    },
    max_memory_restart: '1G'
  }]
};

当你想更新代码时,执行 pm2 reload ecosystem.config.js,它会先让新 worker 起来,再慢慢放掉旧 worker,请求不会瞬间全部断裂。这里要区分 restartreloadpm2 restart 会先停掉当前进程再启动一个新的,单个 worker 时会出现几十到几百毫秒的空窗;pm2 reload 则是滚动重启,逐个替换 worker,用户体验更平滑。

不是所有人都需要 PM2。如果你的服务已经跑在 Kubernetes 或者 Docker Swarm 上,存活检查和重启策略可以由平台负责,再套一个 PM2 反而多了一层状态和复杂度。PM2 更适合裸机、虚拟机和少量容器的部署场景。

选型不是看功能,而是看谁在管

我在不同团队里看到过两种极端:一种是所有服务都扔给 PM2,完全不区分业务特性;另一种是业务完全围绕 Node.js Cluster 模块自己封装,结果 master 进程里攒了不少定制化代码,最后还是在功能上逐渐追赶 PM2。

两种做法都有合理前提。自己封装 cluster,意味着你愿意承担进程调度的控制权。比如某些长连接服务,需要把特定类型的请求绑定到固定 worker,或者多个 worker 承担不同角色,而不是单纯的 HTTP 流量分发。这种情况下,PM2 的 cluster 模式不够灵活,因为它是按“任意请求分配到任意 worker”的统一模型设计的。

反过来,绝大多数 Web API、中间件、消息消费类服务,并不需要这种自定义。它们需要的是稳定性、可观察性和低运维成本,这是 PM2 擅长的地方。它牺牲了一部分底层控制力,但换来了更完整的管理闭环。

对比维度 Cluster 模块 PM2 cluster 模式
底层机制 Node.js 内置多进程 底层调用 cluster,封装管理能力
多核利用 支持 支持
自动重启 需自己实现 内置,可配异常重启
日志处理 按进程输出,需自行聚合 统一采集、按时间切分
部署复杂度 代码控制,复杂度高 配置文件,上手快
适用场景 深度定制进程模型 标准 Web 服务快速上生产

在做决定前,可以问自己四个问题:

  1. 服务是否要求自定义调度策略?如果只是按端口分发,PM2 足够。
  2. 你们是否已有监控和日志体系?如果没有,PM2 能补上最基础的一块。
  3. 部署环境是否能容忍多一层进程守护?容器平台可能会和 PM2 冲突。
  4. 团队更愿意维护 Node.js 代码,还是更愿意维护配置文件?

生产环境里真正容易踩的坑

只看到多进程,忽略了状态同步

做了一个 cluster 服务后,登录状态一直随机失效,这种问题很典型。Node.js 的进程之间不共享内存,如果每个 worker 都维护自己的 session,同一个用户请求分配到不同 worker 时,就会表现得像被登出。解决方案是把 session、缓存等状态放到 Redis 或数据库,让所有 worker 访问同一份数据。这和 PM2 与否无关,只要用了多进程,都要面对。

PM2 不是跨机器负载均衡

PM2 的 cluster 模式只在当前主机内生效。如果你有多台机器,前面没有统一的流量入口,光靠 PM2 无法实现跨机调度。很多团队会误以为加了 instances: 'max' 就是分布式了,其实它只是在单片服务器上尽可能多地跑进程。

master 进程本身也是单点

Cluster 模块的 master 进程如果挂掉,所有 worker 都会失去容器。如果你的“守护方案”只是用 cluster.fork 拉起 worker,那就需要问自己一句:谁来守护 master?PM2 之所以被大量使用,正是因为它作为独立守护进程来监管整个集群,但这也意味着 PM2 进程本身如果退出,服务同样会停。所以生产环境中,常需要把它交给 systemd 或 Docker restart policy 来兜底。

worker 数量不是越多越好

每个 Node.js 进程都有自己的堆空间,假设每个 worker 占 150MB 内存,一个 2GB 的实例最多撑十几个进程。而且进程数超过核数后,上下文切换开销会上升,吞吐量不升反降。instances 的合理值需要结合 CPU、内存、以及上游连接池大小来定。达到极限时,先尝试减少进程数,比盲目加进程更有效。

给生产环境一个务实的路径

如果你正在从零搭建一个普通的 Node.js 服务,我的建议是:默认选择 PM2,优先让它把自动重启和日志聚合落地,再把业务做深。如果你维护的是一个组件型服务,需要在 worker 里做不同的业务分区,再考虑单独使用 Cluster 模块自制调度。

一个实际项目中,我倾向于这样分配:裸机或虚拟机上的多进程,交给 PM2;容器环境里单个容器的进程守护,交给平台本身;只有需要自定义 worker 角色的场景,保留 cluster 代码作为底层能力。这个区分不是为了技术上的洁癖,而是为了降低团队同时维护两套进程治理机制的成本。

长期来看,Node.js 进程管理的重点会慢慢从“怎么启动多个进程”转向“怎么让进程在故障时更快恢复”。无论选 Cluster 模块还是 PM2,建议都补上健康检查、优雅退出和状态外置。这些细节不会立刻被监控系统看到,但它们决定了你在凌晨两点能不能睡个好觉。

原创文章,作者:fudengji,如若转载,请注明出处:https://fudengji.cn/article/606/

(0)
上一篇 1小时前
下一篇 1小时前

相关推荐