Skip to content

Node.js 进程与性能

Node.js 默认是单进程单线程模型。要充分利用多核 CPU、处理 CPU 密集型任务,需要理解 Cluster、Worker Threads 以及性能调优手段。

作者:yanshaodong

单线程模型的优势与瓶颈

  • 优势:无锁竞争、无上下文切换开销、事件循环高效处理 I/O 密集型请求。
  • 瓶颈:单个 CPU 核心只能跑一个事件循环;一旦执行重计算(如大 JSON 解析、加密、图像处理),会阻塞整个事件循环,所有请求都被卡住。

Cluster 多进程

通过 cluster 模块 fork 多个子进程(每个核一个),共享同一端口,由操作系统分发请求,实现多核并行。

javascript
const cluster = require('cluster')
const os = require('os')
const http = require('http')

if (cluster.isPrimary) {
  // 主进程:按 CPU 核心数 fork 子进程
  for (let i = 0; i < os.cpus().length; i++) {
    cluster.fork()
  }
  cluster.on('exit', (worker) => {
    console.log(`进程 ${worker.process.pid} 退出,重启中`)
    cluster.fork()
  })
} else {
  // 子进程:每个都启动一个 HTTP 服务
  http.createServer((req, res) => {
    res.end('handled by ' + process.pid)
  }).listen(3000)
}
  • 主进程不处理业务,只负责调度与故障重启。
  • 子进程之间内存隔离,共享状态需借助外部存储(Redis)或 IPC 通信。

Worker Threads

用于CPU 密集型任务,避免阻塞主线程的事件循环。与 Cluster 不同,Worker 是线程(共享内存可经 SharedArrayBuffer),而非独立进程。

javascript
const { Worker } = require('worker_threads')

// main.js
const worker = new Worker('./compute.js')
worker.on('message', (result) => {
  console.log('计算结果:', result)
})
worker.postMessage({ num: 40 })  // 发送任务

// compute.js
const { parentPort } = require('worker_threads')
parentPort.on('message', ({ num }) => {
  // 此处执行重计算,不会阻塞主线程
  parentPort.postMessage(fibonacci(num))
})

适用场景:加解密、压缩、图像/视频处理、复杂算法。

内存与 CPU 调优

内存限制

Node 默认堆内存上限约为 2GB(旧版 1.5GB),超出会 OOM。

bash
node --max-old-space-size=4096 app.js   # 提升堆上限到 4GB

若应用确实需要超大堆,优先排查内存泄漏,而非一味调大上限。

常见内存泄漏来源

  • 全局变量 / 闭包持有大对象未释放。
  • 未清理的定时器 setInterval
  • 监听器重复绑定(EventEmitter 泄漏)。
  • 缓存无限增长(应使用 LRU 策略)。

性能诊断工具

bash
node --inspect app.js        # 开启调试,Chrome DevTools 远程分析
node --prof app.js           # 生成 V8 profiling 日志

配合 clinic.js0x 等工具可生成火焰图定位热点。

守护与进程管理

生产环境应使用进程管理器保证可用性:

工具特点
PM2最流行,支持集群、日志、监控、零停机重启
Docker + restart policy容器化部署,崩溃自动拉起
systemdLinux 原生服务管理
bash
pm2 start app.js -i max     # 按 CPU 核数启动集群
pm2 monit                   # 实时监控
pm2 reload app.js           # 零停机重启

优雅退出

收到终止信号时,应停止接收新请求、处理完在途请求后再退出。

javascript
process.on('SIGTERM', () => {
  server.close(() => {
    console.log('已停止接收新连接')
    process.exit(0)
  })
  // 超时强退兜底
  setTimeout(() => process.exit(1), 10000).unref()
})

作者:yanshaodong