进程✅
nodejs 进程间如何通信?
答案
核心概念:
- 首选 IPC 通道:使用 child_process.fork/cluster 自带的进程间通道,父子进程可通过 process.send/on('message') 互发结构化数据,并可传递句柄(如 net.Server/socket)。
- 标准流通信:spawn/exec 通过 stdin/stdout 建立字节/文本流,约定 JSON 行协议等实现无状态通信。
- 套接字通信:使用 net/dgram 建立 TCP/UDP 或 Unix Domain Socket,适合跨进程/跨主机与多语言互通。
- 能力边界:SharedArrayBuffer 仅线程间可共享(worker_threads),进程间不共享;需要通过 IPC/Socket/文件等媒介。
- 可靠性:定义消息协议(类型、序列号、超时与重试),避免竞态;大对象注意复制与背压。
- Fork IPC
- Cluster IPC
- 句柄传递
- stdin/stdou
- SharedArrayBuffer (worker)
- TCP Socket
- UDP dgram
- Domain Socket
- 主进程通过返回的子进程实例监听子进程回传事件
- 子进程通过主进程传入的环境变量识别自身
- 子进程基于 process.send 发送事件, proccess.on('message') 监听主进程消息
Terminal
- cluster.isPrimary 判断当前进程是否为主进程
- cluster.fork() 创建子进程,通过 cluster.workers 获取所有子进程实例
- 子进程基于 process.send 发送事件, process.on('message') 监听主进程消息
Terminal
- 主进程直接将句柄传递给子进程,子进程直接监听句柄实现负载均衡
Terminal
- 主进程通过子进程的标准输入输出流与其通信
Terminal
- 同进程多线程共享内存:使用 SharedArrayBuffer + Atomics 在线程间共享数据,适合低开销并行与计数/环形缓冲区等场景
- 主进程创建 socket server,子进程通过连接服务端通过 socket 进行通信
Terminal
- 主进程创建 dgram socket server,子进程通过连接服务端进行通信
Terminal
Terminal
提示
process.send 仅在通过 fork/cluster 或 spawn({stdio: ['pipe','pipe','pipe','ipc']})
时可用;spawn/exec 默认没有 IPC 通道,仅有标准流。
延伸阅读
- Node.js child_process — fork/spawn/exec 与 IPC 通道、句柄传递
- Node.js cluster — 多进程模型与主从进程通信
- Node.js process — process.send 与消息事件
- Node.js net — TCP/Unix Domain Socket 通信
- Node.js dgram — UDP 报文通信,适合无连接场景
worker、child_process、cluster 是什么,有什么区别?
答案
- worker_threads:同进程多线程,线程间共享内存(SharedArrayBuffer/Atomics),消息通道 MessagePort,适合 CPU 密集与低开销并行。
- child_process:独立进程(spawn/exec/fork),进程级隔离,IPC or stdio 通信,可运行非 Node 命令,适合隔离、调用系统工具。
- cluster:基于 child_process.fork 的多进程模型,主进程统一监听端口并做句柄分发/负载均衡,便于横向扩容利用多核。
对比项 | worker_threads | child_process | cluster |
---|---|---|---|
隔离 | 线程级,共享内存 | 进程级,完全隔离 | 多进程,主从模型 |
通信 | MessagePort、共享内存 | IPC/stdio/Socket | IPC(封装在 cluster) |
开销 | 低(创建/切换轻) | 高(创建/内存独立) | 中(管理便捷) |
场景 | CPU 密集、数据共享 | 外部命令、隔离容错 | HTTP 服务多核扩展 |
示例说明
- worker
- child_process
- cluster
Terminal
Terminal
Terminal
延伸阅读:
- Node.js worker_threads — 线程、MessagePort、SharedArrayBuffer
- Node.js child_process — spawn/exec/fork 与 IPC/stdio
- Node.js cluster — 多进程模型与句柄分发
- Node.js net — 句柄传递与服务端复用端口(负载均衡基础)
用过 cluster 讲一下?
答案
核心概念:
- 定位:cluster 基于 child_process.fork 的主-工多进程模型,用多进程利用多核,主进程统一管理与调度。
- 句柄分发:主进程集中持有 server 句柄并将连接分配给各 worker(默认轮询),单端口对外、内部多进程处理。
- 通信与治理:主/工通过 IPC 消息通信,可传递句柄;关注 worker 的 online/listening/exit 事件实现自愈。
- 适用与边界:I/O 密集型 HTTP 服务横向扩展;有状态会话需粘滞或外部会话;CPU 密集更适合 worker_threads。
示例说明:
提示
默认调度策略为轮询(SCHED_RR),也可通过环境变量 NODE_CLUSTER_SCHED_POLICY 控制。会话粘滞可用四层代理(如 Nginx IP hash)或应用层粘滞实现。
延伸阅读:
- Node.js cluster — 主-工模型、调度策略、生命周期事件
- Node.js child_process — fork/IPC 基础
- Node.js net — server 句柄与连接分发
- Node.js worker_threads — CPU 密集并行的替代方案
Node 创建子进程方式有几种,有哪些区别?
答案
- spawn:基于流的进程创建,stdout/stderr 为流,适合长时间、长输出与管道场景;不经 shell,安全可控。
- exec:经 shell 执行命令,缓冲整段输出后一次返回,简单但有 maxBuffer 与 shell 注入风险。
- execFile:不经 shell 直接执行可执行文件,等同 spawn 的简化封装,适合短输出的“命令+参数”。
- fork:仅用于 Node 模块,自动建立 IPC 通道(process.send/on),可传递句柄,适合父子进程结构化通信。
方式 | 是否经 shell | 输出形式 | 通信 | 典型场景 |
---|---|---|---|---|
spawn | 否 | 流式(边产边读) | 管道/自建协议 | 长跑任务、实时输出、管道 |
exec | 是 | 缓冲(收齐返回) | 回调返回整块 | 短命令、脚本一把梭 |
execFile | 否 | 缓冲(整块) | 回调返回整块 | 直接执行二进制/Node |
fork | 否(Node 专用) | IPC 消息 | process.send/on | 多进程协作、句柄传递 |
示例说明
- fork
- exec
- execFile
- spawn
Terminal
Terminal
Terminal
Terminal
提示
长输出与实时日志用 spawn(流式、低内存);需要结构化父子通信用 fork。使用 exec 时留意 maxBuffer 与 shell 注入风险;无需 shell 请优先 execFile/spawn。
延伸阅读:
- Node.js child_process — 四种创建方式与选项
- Node.js process.send — fork 的 IPC 通道
- Security: Shell Injection — shell 注入风险与规避
child.kill
与 child.send
有哪些区别?
答案
结论:child.send 用于在具备 IPC 通道的父子进程间传递结构化数据与句柄;child.kill 用于向任意进程发送操作系统信号以控制其生命周期,不能承载数据。
维度 | child.send | child.kill | 备注 |
---|---|---|---|
语义 | IPC 消息 | OS 信号 | 本质不同:数据通道 vs 控制信号 |
前提 | 需 IPC 通道(fork/cluster 或 spawn 含 'ipc') | 只要有 PID 即可 | spawn/exec 默认无 IPC |
载荷 | 对象/Buffer/句柄(server/socket) | 仅信号,无数据 | 句柄传递只能用 send |
影响 | 不结束进程,仅通信 | 可能触发退出(如 SIGTERM) | SIGKILL/SIGSTOP 不可捕获 |
跨平台 | 一致 | Windows 支持有限 | Win 常见 SIGTERM/SIGINT/SIGKILL |
返回值 | 布尔:写入队列是否成功 | 布尔:信号是否发送 | send 为异步投递语义 |
延伸阅读
- Node.js child_process.send — IPC 消息与句柄传递
- Node.js child_process.kill — 发送信号与平台差异
- Node.js process.kill — 按 PID 发送信号
- Node.js Signals — 信号事件与可捕获性
child_process.fork 与 POSIX 的 fork 有什么区别?
答案
- POSIX fork:内核复制当前进程(写时复制 COW),父子共享打开的 FD,从 fork 返回继续执行;无自动加载新程序,常与 execve 组合。
- child_process.fork:启动一个新的 Node 进程加载目标模块,自动建立 IPC 通道(process.send/on),非内存复制语义,跨平台可用。
- 能力差异:POSIX fork 可继承父进程完整状态(地址空间/FD);Node fork 仅启动新解释器与模块,无法共享内存(除显式 IPC/Socket)。
- 选型:应用层多进程协作与消息通信用 child_process.fork;系统级进程复制/热继承需用原生 fork/exec(Node 不直接暴露)。
维度 | POSIX fork | child_process.fork |
---|---|---|
执行语义 | 复制当前进程+COW,原地返回 | 启动新 Node 进程加载模块 |
IPC | 无自动 IPC | 内置 IPC(process.send/on) |
FD/句柄 | 继承父进程已打开 FD | 不继承,支持通过 send 传句柄 |
跨平台 | Unix 专有 | 跨平台(Windows/Unix) |
典型用法 | fork→exec 链 | 多进程 Node 服务与治理 |
- child_process.fork
- POSIX fork
#!/usr/bin/env bash
# posix_fork_exec.sh
#
# 目的:用可观测的方式说明 POSIX fork + exec 的典型流程。
# 说明:
# - fork() 复制当前进程(写时复制),父子进程从 fork 返回,返回值不同
# - 子进程通常紧跟着 exec*() 族调用加载新程序(替换自身镜像)
# - 这里用 bash 来模拟展示流程和形态特征(非内部实现),方便在任意 Unix 环境演示
#
# 用法:
# bash posix_fork_exec.sh
set -euo pipefail
parent_pid=$$
echo "[parent $$] start"
# fork: 在 bash 中用 subshell/后台作业演示分歧(语义并非内核 fork 实现,仅用于解释流程)
(
echo "[child $$] after fork: I'm child (return value == 0)"
echo "[child $$] now exec into /bin/echo (replace image)"
exec /bin/echo "[child $$] hello from new program via exec"
) &
child_pid=$!
echo "[parent $$] after fork: created child pid ${child_pid} (return value == ${child_pid})"
wait ${child_pid}
echo "[parent $$] child ${child_pid} exited"
延伸阅读:
- man 2 fork — 进程复制与 COW 语义
- man 2 execve — 加载新程序的系统调用
- Node.js child_process.fork — Node 专用 fork 与 IPC
- Node.js ChildProcess — spawn/exec/execFile/fork 对比