Node.js 进程与子进程:child_process、cluster 与 worker_threads

前面几篇都在强调:默认跑 JS 的主线程不适合扛重 CPU。真的要把算力铺开,就要离开「单进程单线程」的舒适区。
这一篇围绕 进程与线程 展开,想讲清楚几件事:child_processworker_threads 各自解决什么问题、cluster 和多进程监听同一端口时发生了什么,以及选型时怎么避免过度设计。

process 对象提供的是 当前 Node 进程 的视图:

  • process.env:环境变量(配置与密钥注入的常见入口);
  • process.argv:命令行参数;
  • process.exit(code):退出码(运维与容器健康检查会看);
  • 各类 process.on(...) 信号与生命周期事件。

它不「创建」别的进程,但 是每个进程里都要打交道的一层壳

child_process 用来启动 另一个操作系统进程(可以是另一个 Node,也可以是任意命令):

  • spawn:流式stdio,适合长时间任务、大输出;
  • exec / execFile:缓冲输出,适合短命令(注意输出大小上限);
  • fork:专门 fork 一个 Node 子进程,并带上 IPC 通道。

特点:

  • 内存不共享:父子之间传数据要 序列化(JSON、Buffer),大对象来回拷贝会贵;
  • 崩溃隔离好:子进程挂了,父进程还能决定要不要重启;
  • 启动成本比线程高。

典型用途:

  • 跑外部工具(图像处理、ffmpeg、git);
  • CPU 重任务整包丢到子进程;
  • 不同权限不同 Node 版本 的任务隔离。

worker_threads 在同一个进程里开 额外 V8 隔离环境Worker):

  • 每个 Worker 有自己的 事件循环JS 堆
  • 与主线程通过 MessageChannel 传消息;
  • 可以转移 SharedArrayBuffer 做真正的共享内存(有场景与安全限制)。

child_process 的取舍可以粗记:

  • 要并行算、又要少拷贝:优先考虑 ** transferable 的 Buffer / SAB** + Worker;
  • 要强隔离、跑别的二进制:用 child_process

cluster(或手动起多进程 + 负载均衡)解决的是:

  • 单进程单线程 JS 吃不满多核 CPU;
  • 通过 多个进程 各自跑一份服务,前面用 操作系统或反向代理 分发连接。

cluster 模块的经典模式是:

  • 主进程 fork 多个 worker
  • worker 里跑同样的 HTTP Server
  • 主进程负责 把 TCP 连接派给某个 worker(实现细节随版本以文档为准)。

要注意:

  • 内存状态不共享:会话、缓存要么外置(Redis)、要么用 粘性会话 / 无状态设计
  • 日志与调试会变复杂:要带上 进程 id / worker id

可以按问题类型粗分:

  • I/O 等外部系统:多进程未必有用,先优化连接、池、超时与异步;
  • 单请求内重 CPUWorker子进程 把那段挪出去;
  • 整体 QPS 顶满单核水平扩展进程 + 负载均衡。

过度使用 Worker / 子进程也会:

  • 增加 调度开销
  • 状态同步 变难。
  • child_process:进程级隔离,适合外部命令与强隔离重任务;
  • worker_threads:进程内并行,适合可控的 CPU 并行与数据传递;
  • cluster / 多进程部署:把「一个 Node 进程」扩展成「一排」,要配合无状态与服务发现。

把这三块放在一张 mental map 里,后面接消息队列、微服务、容器编排时,会自然知道「该在哪一层拆进程」。