游乐游手机版
首页/AI教程/文章详情

Node.js多进程集群部署,别让单进程扛整机

时间:2026-07-01 14:54
Node js多进程和Cluster集群部署,别让一个进程扛完整台机器 Node js 的单进程模型在理解上非常直观,但在生产环境中踩坑的姿势也相当丰富。 想象一下:你在一台 8 核机器上运行一个 Express 服务,没有做任何特殊处理。真正干活的 JavaScript 引擎只有一个进程。CPU

Node.js多进程和Cluster集群部署,别让一个进程扛完整台机器

Node.js 的单进程模型在理解上非常直观,但在生产环境中踩坑的姿势也相当丰富。

Node.js多进程和 Cluster 集群部署,别让一个进程扛完整台机器

想象一下:你在一台 8 核机器上运行一个 Express 服务,没有做任何特殊处理。真正干活的 JavaScript 引擎只有一个进程。CPU 一号核心已经累垮了,而旁边的几个核心却悠闲得像在围观。线上流量一旦上来,表象就很诡异:机器整体 CPU 看起来并不高,但接口的 p99 延迟已经开始剧烈抖动。

这篇文章专门讲解 Node.js 的多进程方案和 Cluster 集群部署。环境采用 Node.js 24 与 Express 5.2.1。我们先使用 Node 官方 cluster 模块搭建一个可运行的示例,然后讨论它的边界在哪里,以及为什么生产环境中很多团队最终选择使用 PM2、systemd、Docker 或 Kubernetes 来管理进程。

Cluster 解决的核心问题

Node 官方文档对 cluster 的描述非常直白:它能够创建一组子进程,并且这些子进程可以共享同一个服务器端口。

这里的关键词是——进程。

Cluster 不是线程池,也不是把一个请求拆给多个核心并行计算。它的工作方式更像这样:主进程负责启动 worker,多个 worker 都监听同一个端口,请求进入后,由主进程分发给其中一个 worker 去处理。

这样做直接带来两个好处:

  • 多核 CPU 终于能被充分利用起来。
  • 某个 worker 崩溃后,主进程可以立刻启动一个新的。

代价也很现实:进程之间内存不共享。你存放在内存里的登录态、计数器、缓存,每个 worker 都各自有一份。一旦上了多进程,就千万别再将进程内存当作全局真理。

一个最小可运行的 Cluster 服务

首先安装 Express:

mkdir node-cluster-demo
cd node-cluster-demo
npm init -y
npm install express

server.js

const cluster = require('node:cluster')
const os = require('node:os')
const process = require('node:process')
const express = require('express')

const port = Number(process.env.PORT || 3000)
const workerCount = Number(process.env.WORKERS || os.a vailableParallelism())

if (cluster.isPrimary) {
  console.log(`primary ${ process.pid} is running`)
  console.log(`starting ${ workerCount} workers`)
  for (let i = 0; i < workerCount; i  ) {
    cluster.fork()
  }
  cluster.on('exit', (worker, code, signal) => {
    console.error(`worker ${ worker.process.pid} died`, {code, signal })
    cluster.fork()
  })
} else {
  const app = express()
  app.get('/health', (req, res) => {
    res.json({ ok: true, pid: process.pid,})
  })
  app.get('/cpu', (req, res) => {
    const startedAt = Date.now()
    while (Date.now() - startedAt < 80) {
      Math.sqrt(Math.random())
    }
    res.json({ ok: true, pid: process.pid,})
  })
  app.listen(port, () => {
    console.log(`worker ${ process.pid} listening on https://localhost:${ port}`)
  })
}

启动服务:

node server.js

多请求几次:

curl https://localhost:3000/health
curl https://localhost:3000/health
curl https://localhost:3000/health

你会看到返回的 pid 可能不同——说明请求被分配到了不同的 worker 进程上。

这里特意用了 os.a vailableParallelism(),而不是以前习惯的 os.cpus().length。Node 官方在 os.cpus() 文档里也曾提醒过,不应拿它来计算应用可用并行度,推荐直接使用 os.a vailableParallelism()

生产代码不能只会 fork

上面的代码能运行,但离生产环境还有好几步。至少需要处理优雅退出:

function createApp() {
  const app = express()
  app.get('/health', (req, res) => {
    res.json({ok: true, pid: process.pid })
  })
  return app
}

if (cluster.isPrimary) {
  for (let i = 0; i < workerCount; i  ) {
    cluster.fork()
  }
  cluster.on('exit', (worker, code, signal) => {
    if (worker.exitedAfterDisconnect) { return }
    console.error(`worker ${ worker.process.pid} crashed`, {code, signal })
    cluster.fork()
  })
} else {
  const app = createApp()
  const server = app.listen(port)
  process.on('SIGTERM', () => {
    server.close(() => {
      process.exit(0)
    })
    setTimeout(() => {
      process.exit(1)
    }, 10_000).unref()
  })
}

server.close() 会停止接收新连接,等已有的连接处理完成后再退出。外面再加一个 10 秒的兜底,是为了防止某些长连接或异常请求导致进程永远退不掉。

主进程里也别无脑重启所有退出的 worker。worker.exitedAfterDisconnect 这个属性可以帮助你区分“主动要求它退出”和“它意外崩溃”。这在滚动重启时尤其有用。

状态别放在进程内存里

这是 Cluster 最容易踩坑的地方。

假设你写了一个简单计数器:

let counter = 0
app.post('/count', (req, res) => {
  counter  = 1
  res.json({counter, pid: process.pid })
})

单进程时它看起来没问题。多进程后,每个 worker 都有自己的 counter。请求打到 worker A,counter 是 10;下一个请求打到 worker B,counter 可能是 3。你以为自己写了全局计数器,实际上写了 N 个局部计数器。

登录态也一样。不要把 session 存在进程内存里,然后指望 Cluster 替你同步。应该放到 Redis、数据库,或者使用无状态 token。进程内缓存也要接受一个现实:每个 worker 都会各自缓存一份,命中率和内存占用都会受到影响。

曾经见过一个后台系统,上了 Cluster 之后验证码偶发校验失败。原因很朴实:验证码存在内存 Map 里,生成请求落到 worker 1,校验请求落到 worker 3,自然找不到了。

负载均衡不是魔法

Node cluster 支持两种分发方式。官方文档提到,除 Windows 外,默认是 round-robin,由主进程接收连接再分发给 worker。另一种方式是主进程创建监听 socket 后交给 worker,由 worker 自己 accept。

日常业务一般不用手动修改 cluster.schedulingPolicy。真正需要关心的是:worker 之间的负载可能仍然不均匀。

原因有很多:

  • 请求耗时不同,慢请求会长期占用某个 worker
  • 长连接(比如 WebSocket)会让连接长时间停留在某个 worker 上
  • CPU 密集逻辑会阻塞单个 worker 的 event loop

所以 Cluster 并不是性能银弹。它只是让你把请求分散到多个进程。如果某个接口本身写得很重,多进程能缓解,但不能从根本上解决问题。

多进程下日志和监控要带 pid

上了 Cluster 之后,日志里必须带上 pid 或 worker id:

logger.info('request finished', {
  pid: process.pid,
  workerId: cluster.worker?.id,
  method: req.method,
  path: req.originalUrl,
  status: res.statusCode,
})

否则你看到一段错误日志,很难判断是不是某一个 worker 持续出问题。比如 worker 4 内存一直上涨,最后反复重启。总览日志里只看到“进程重启了”,却看不到是哪一个,定位起来非常困难。

监控也一样。除了进程整体指标,我建议给每个 worker 都打上:

  • RSS、heapUsed
  • event loop delay
  • 请求数和错误数
  • 重启次数

多进程系统最怕平均值。平均 CPU 不高,不代表每个 worker 都健康;平均内存正常,也可能其中一个 worker 正在泄漏。

Cluster 和 PM2、容器怎么取舍

如果只是想理解原理,Node 内置 cluster 完全够用。

如果是生产部署,我的建议是按环境选择:

  • 传统服务器:可以用 PM2 的 cluster mode 或 systemd 管理多个进程。PM2 省心,日志、重启、进程列表都有现成命令。systemd 更贴近系统层,适合不想引入额外 Node 进程管理工具的团队。
  • Docker / Kubernetes 环境:我更倾向于一个容器只跑一个 Node 进程,然后通过多个副本来扩容。进程重启、健康检查、滚动发布、日志采集都交给平台。也可以在容器内再开 Cluster,但这样会让资源限制、优雅退出、监控粒度变得复杂。不是不能做,只是要有明确的理由。
  • 本地开发和小型内网服务:直接用 cluster 也够。但别把它当成完整的进程平台——它不负责日志采集、发布编排、健康检查、限流熔断,也不会替你处理共享状态。

什么场景不适合只靠 Cluster

下面几种情况,纯 Cluster 往往搞不定:

  • CPU 重任务:Cluster 能把请求分到多个进程,但单个请求里的 CPU 计算还是会堵住对应 worker。重计算更适合 Worker Threads、任务队列,或者拆成独立服务。
  • 大量 WebSocket 长连接:连接会固定在某个 worker 上,负载均衡和会话管理需要额外设计。多实例部署时还要考虑消息广播和房间状态。
  • 强依赖本地内存状态的应用:比如用本地 Map 存 session、验证码、临时任务状态。先把状态外置,再谈多进程。
  • 超短任务但日志极重的服务:进程多了,日志竞争和 IO 压力也会上来。这时要先控制日志量。

收尾

Cluster 的价值很朴素:让 Node 服务用上多核,并且给 worker 崩溃后的恢复留一个入口。

但它也会逼你面对几个工程事实:

  • 进程内存不是共享状态
  • 日志和监控必须能区分 worker
  • 优雅退出要自己处理
  • 多进程只能缓解单进程瓶颈,不能修好糟糕的同步代码

我的建议是:先用 cluster 把多进程模型跑明白,再根据部署环境决定交给谁管理。传统机器上用 PM2 或 systemd,容器环境交给 Kubernetes 或编排平台。无论选择哪条路,都别让一个 Node 进程孤零零扛完整台机器。

参考来源

  • Node.js cluster 官方文档:cluster 工作模型、cluster.isPrimarycluster.fork()exit 事件、调度策略,采集于 2026-06-29
  • Node.js os 官方文档:os.a vailableParallelism()os.cpus() 的使用建议,采集于 2026-06-29
  • Node.js process 官方文档:SIGTERM、进程事件与退出处理,采集于 2026-06-29
  • npm 元数据:express@5.2.1,采集于 2026-06-29
来源:https://developer.aliyun.com/article/1744477
上一篇机器人强化学习中HIL-SERL算法的DQN与SAC混合架构实现哲学 下一篇工厂设备资产维保一体化解决方案助力降本增效
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
RAG四标融合企业知识资产体系四库协同GEO优化实践
AI教程 · 2026-07-01

RAG四标融合企业知识资产体系四库协同GEO优化实践

生成式AI正在彻底改写信息检索的底层逻辑。传统SEO依赖关键词堆砌和外链建设的策略,在大模型的内容采信规则下已经基本失效。取而代之的,是生成式引擎优化(GEO)。它不再关注外链数量,而是重点衡量你的知识是否结构化、证据链是否坚实、信源是否可靠——这些维度才是RAG(检索增强生成)架构真正看重的核心指

一个普通上班人分享WorkBuddy使用心得与真实体验
AI教程 · 2026-07-01

一个普通上班人分享WorkBuddy使用心得与真实体验

前言 最近我开始使用WorkBuddy——这是腾讯推出的一款AI办公工作台。差不多用了一周时间,趁印象还新鲜,把真实的使用感受记录下来,给还在犹豫的朋友做个参考。不吹不黑,只说实际体验。 初印象:不只是聊天机器人 之前用过不少AI工具,大多数就是个对话框,你问它答,答完就结束了。WorkBuddy不

AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录
AI教程 · 2026-07-01

AI幻觉变真功能实战教程:App Inventor 2视频录制拓展一周开发实录

先讲一个颇具戏剧性的开端。 这件事的开端颇显荒诞——有用户前来咨询,称AI Pro版的介绍中提到我们有一款“视频录制拓展”。团队全体成员都感到困惑,翻遍产品列表,发现根本不存在该组件。AI那种“一本正经胡说八道”的能力,这次确实让我们陷入尴尬。 按常理,此事到此便可结束——一句“抱歉,暂时没有这个拓

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同
AI教程 · 2026-07-01

别再混淆OLAP和SQL-on-Hadoop两者查询本质不同

OLAP和SQL-on-Hadoop虽都使用SQL查询数据,但本质不同。SQL-on-Hadoop负责海量数据批量计算与ETL,查询速度秒级至分钟级;OLAP通过预聚合实现毫秒级多维分析,适合BI报表。两者在数据平台分工协作,前者是后厨加工,后者是前台快速服务。

GEO优化深度解析:AI偏好FAQ还是长文内容?
AI教程 · 2026-07-01

GEO优化深度解析:AI偏好FAQ还是长文内容?

在GEO优化中,AI对内容形式无统一偏好:FAQ在简单查询中引用率41%,长文在复杂查询中达58%。内容应基于用户意图选择形式,FAQ适配简单事实类问题,长文建立主题权威,两者互补而非替代。