Node.js 多进程:child_process 与 cluster 榨干多核 CPU
Node.js 多进程:child_process 与 cluster 榨干多核 CPU
Node 的事件循环跑在单个线程上,一台 8 核机器默认只用一个核。CPU 密集场景或者纯粹想吃满机器性能,就得开多进程。Node 给了两层方案:底层的 child_process 和上层的 cluster。
child_process:四种创建方式
const { spawn, exec, execFile, fork } = require('child_process')
// 1. spawn:启动任意命令,流式读写输出,适合长任务
const ls = spawn('ls', ['-la'])
ls.stdout.on('data', (chunk) => console.log(chunk.toString()))
// 2. exec:执行 shell 字符串,一次性缓冲全部输出,适合短命令
exec('git log --oneline -5', (err, stdout) => {
console.log(stdout)
})
// 3. fork:专门 fork 一个新的 Node 进程,自带 IPC 通道
const worker = fork('./worker.js')
worker.send({ task: 'compress', file: 'a.zip' })
worker.on('message', (result) => console.log(result))
子进程里用 process.on('message') 接收、process.send() 回传,父子之间靠 IPC 通信,不用走网络。
经验法则:
- 调用系统命令、第三方 CLI:spawn(参数数组传参,避免 shell 注入);要 shell 特性(管道、通配符)才用 exec,且绝不要把用户输入拼进 exec 的命令串
- 跑一段独立的 Node 计算任务:fork,IPC 通信最方便
cluster:多进程监听同一个端口
fork 的子进程默认不能复用父进程的端口,而 cluster 模块就是为解决"多个进程共同对外提供 HTTP 服务"而生的:
const cluster = require('cluster')
const http = require('http')
const os = require('os')
if (cluster.isPrimary) {
// 主进程:按 CPU 核数 fork worker
const numCPUs = os.cpus().length
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
// worker 挂了自动拉起,实现基本的守护
cluster.on('exit', (worker) => {
console.log('worker ' + worker.process.pid + ' 挂了,重启')
cluster.fork()
})
} else {
// 工作进程:各自跑服务,但监听同一个端口
http.createServer((req, res) => {
res.end('handled by ' + process.pid)
}).listen(3000)
}
多个进程 listen 同一端口不报错,是因为连接先到达主进程,由主进程(Linux 下默认 round-robin)分发给 worker。Windows 上是各 worker 竞争 accept,行为略不同。
主进程不处理业务请求,只做调度和守护,轻量且稳定——挂了自动重启这个机制放在主进程里最可靠。
生产环境:直接用 PM2
手写 cluster 适合学习原理,生产上一般用 PM2 一行搞定,还附赠日志管理、开机自启、监控:
pm2 start app.js -i max # max = 按 CPU 核数起实例
pm2 reload app # 滚动重启,逐个实例替换,服务不中断
pm2 monit # 实时看各实例 CPU / 内存
reload 的滚动重启比 restart 重要:restart 是把所有实例同时干掉再起,中间有服务真空期;reload 逐个替换,对外无感。
什么时候需要多进程
- CPU 密集计算:图像处理、加解密、大 JSON 序列化——这些会阻塞事件循环,丢给 worker 进程或 worker_threads
- 吃满多核:单机部署 Node 服务,不做多进程等于浪费大半台机器
- 故障隔离:单个 worker 崩溃不影响整体
反过来,如果服务是纯 IO 型(转发请求、查数据库),Node 的异步 IO 本身就能扛很高并发,多进程更多是为了利用多核而不是解决阻塞。
顺带一提:如果只是要并行执行 JS 计算而不需要独立进程,worker_threads(共享内存、线程级)比 fork 进程更轻量,这是另一个话题了。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~