Node.js 进程与子进程:child_process、cluster 与 worker_threads
前面几篇都在强调:默认跑 JS 的主线程不适合扛重 CPU。真的要把算力铺开,就要离开「单进程单线程」的舒适区。
这一篇围绕 进程与线程 展开,想讲清楚几件事:child_process和worker_threads各自解决什么问题、cluster和多进程监听同一端口时发生了什么,以及选型时怎么避免过度设计。
process:当前进程的环境与控制台
process 对象提供的是 当前 Node 进程 的视图:
process.env:环境变量(配置与密钥注入的常见入口);process.argv:命令行参数;process.exit(code):退出码(运维与容器健康检查会看);- 各类
process.on(...)信号与生命周期事件。
它不「创建」别的进程,但 是每个进程里都要打交道的一层壳。
child_process:新进程、新 V8、隔离最强
child_process 用来启动 另一个操作系统进程(可以是另一个 Node,也可以是任意命令):
spawn:流式stdio,适合长时间任务、大输出;exec/execFile:缓冲输出,适合短命令(注意输出大小上限);fork:专门 fork 一个 Node 子进程,并带上 IPC 通道。
特点:
- 内存不共享:父子之间传数据要 序列化(JSON、Buffer),大对象来回拷贝会贵;
- 崩溃隔离好:子进程挂了,父进程还能决定要不要重启;
- 启动成本比线程高。
典型用途:
- 跑外部工具(图像处理、ffmpeg、git);
- CPU 重任务整包丢到子进程;
- 不同权限或 不同 Node 版本 的任务隔离。
worker_threads:共享 ArrayBuffer,更像「并行 JS」
worker_threads 在同一个进程里开 额外 V8 隔离环境(Worker):
- 每个 Worker 有自己的 事件循环 和 JS 堆;
- 与主线程通过
MessageChannel传消息; - 可以转移
SharedArrayBuffer做真正的共享内存(有场景与安全限制)。
和 child_process 的取舍可以粗记:
- 要并行算、又要少拷贝:优先考虑 ** transferable 的 Buffer / SAB** + Worker;
- 要强隔离、跑别的二进制:用 child_process。
cluster:多进程吃满多核,外加调度
cluster(或手动起多进程 + 负载均衡)解决的是:
- 单进程单线程 JS 吃不满多核 CPU;
- 通过 多个进程 各自跑一份服务,前面用 操作系统或反向代理 分发连接。
cluster 模块的经典模式是:
- 主进程
fork多个 worker; - worker 里跑同样的 HTTP
Server; - 主进程负责 把 TCP 连接派给某个 worker(实现细节随版本以文档为准)。
要注意:
- 内存状态不共享:会话、缓存要么外置(Redis)、要么用 粘性会话 / 无状态设计;
- 日志与调试会变复杂:要带上 进程 id / worker id。
选型时别混在一起:先问「瓶颈在哪」
可以按问题类型粗分:
- I/O 等外部系统:多进程未必有用,先优化连接、池、超时与异步;
- 单请求内重 CPU:Worker 或 子进程 把那段挪出去;
- 整体 QPS 顶满单核:水平扩展进程 + 负载均衡。
过度使用 Worker / 子进程也会:
- 增加 调度开销;
- 让 状态同步 变难。
小结:隔离与并行都有代价
child_process:进程级隔离,适合外部命令与强隔离重任务;worker_threads:进程内并行,适合可控的 CPU 并行与数据传递;cluster/ 多进程部署:把「一个 Node 进程」扩展成「一排」,要配合无状态与服务发现。
把这三块放在一张 mental map 里,后面接消息队列、微服务、容器编排时,会自然知道「该在哪一层拆进程」。