Node.js 开发手册NODE.JS

CHAPTER 09生产层 · PERFORMANCE

性能与部署

cluster、worker_threads、Stream、PM2、Docker、诊断(perf_hooks/0x)与监控。把服务稳稳送上生产。

开始阅读难度高阶先修全本手册约2.5小时
09.1

单线程与多核

Node 主线程是单条事件循环,默认只用到一个 CPU 核。要榨干多核,需用 cluster(多进程)或 worker_threads(多线程)。

方案隔离通信适合
cluster进程级IPC 消息HTTP 多实例负载
worker_threads线程级MessageChannelCPU 密集计算
09.2

cluster 多进程

cluster 为每个 CPU 核 fork 一个 worker,共享同一端口,由主进程分发连接。一个 worker 崩溃不影响其他。

import cluster from 'node:cluster';
import os from 'node:os';
if (cluster.isPrimary) {
  for (let i = 0; i < os.cpus().length; i++) cluster.fork();
  cluster.on('exit', w => cluster.fork());   // 自动重启
} else {
  app.listen(3000);
}
09.3

worker_threads

CPU 密集任务(压缩、加密、大计算)放进 worker,避免阻塞主事件循环。worker 间通过 postMessage 通信。

import { Worker } from 'node:worker_threads';
const w = new Worker(`
  parentPort.on('message', d => parentPort.postMessage(d * 2));
`, { eval: true });
w.on('message', r => console.log('结果', r));
w.postMessage(21);
选 cluster 还是 worker

I/O 密集的 HTTP 服务用 cluster 多实例即可;重计算用 worker_threads 把活移出主线程。

09.4

Stream 流式

处理大文件/大响应时,用流逐块读写,内存占用恒定,不会因为"一次性载入"而 OOM。

import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';
createReadStream('big.log')
  .pipe(createGzip())
  .pipe(createWriteStream('big.log.gz'));   // 全程流式,内存稳定
09.5

诊断 perf_hooks

用 perf_hooks 测量耗时;用 0x / Clinic.js 做火焰图,定位最耗时的函数。

import { performance } from 'node:perf_hooks';
const t = performance.now();
await heavyWork();
console.log('耗时', (performance.now() - t).toFixed(1), 'ms');
09.6

PM2 进程管理

PM2 是经典的生产进程管理器:守护、集群、日志、零停机重启。适合裸机/虚拟机部署。

npm i -g pm2
pm2 start dist/index.js -i max     # 按核数起集群
pm2 logs                           # 查看日志
pm2 reload all                     # 零停机重启
09.7

Docker 与优雅退出

容器化让环境一致。务必处理 SIGTERM 优雅退出:停止接收新请求、排空在途请求、关闭连接池。

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY dist ./dist
USER node
CMD ["node", "dist/index.js"]
process.on('SIGTERM', async () => {
  server.close();          // 停止接新连接
  await pool.end();         // 关闭数据库连接
  process.exit(0);
});
优雅退出不可省

K8s 发 SIGTERM 后会给宽限期。不处理会导致在途请求被硬切断、连接泄漏。

09.8

常见陷阱与 FAQ

cluster 每个 worker 共享内存吗?

不共享。worker 是独立进程,内存隔离。需要共享状态用外部存储(Redis)或 IPC 消息。

为什么用了 cluster 还是卡?

如果是单条重计算阻塞了主线程,cluster 只是多份副本,单个请求仍慢。需把计算移入 worker_threads。

Docker 里进程退出码 137?

通常是 OOM 被 kill。检查内存泄漏、未流式处理的大对象,并合理设置容器内存上限。

09.9

实战练习与资源

延伸资源