跳到主要内容

性能参考

Fastify vs Express​

NestJS 默认使用 Express,切换 Fastify 后核心收益:

指标ExpressFastify差异
吞吐量 (req/s)~15,000~30,000≈ 2 倍
平均延迟~6.5ms~3.2ms≈ 50% 降低
P99 延迟~25ms~12ms≈ 52% 降低
内存占用基准-10% ~ -15%更低

数据来自 Fastify 官方基准测试(Node.js 22,简单 JSON 响应)。实际业务场景差异取决于下游调用与序列化开销。

WebSocket 长连接容量​

单实例 Node.js 服务的理论容量(4C8G 容器):

指标数值约束条件
活跃连接数10,000 - 50,000消息频率低、心跳间隔 25s
消息吞吐量5,000 - 10,000 msg/s消息体 ≤ 1KB,无复杂序列化
内存占用/连接~50KB含 Socket.IO 内部状态与缓冲区
CPU 占用60% - 70%消息编解码与房间匹配

容量红线:

  • 单实例连接数 > 30,000 时,内存与事件循环延迟显著上升,建议水平扩展
  • 消息体 > 10KB 或包含二进制数据时,吞吐量下降 50% 以上

GraphQL 开销​

GraphQL 的灵活性带来额外解析与校验成本:

场景开销来源优化
查询解析Schema 校验、字段解析启用 validationRules 缓存(Apollo 默认)
N+1 查询字段级 Resolver 重复调用下游DataLoader 批处理
深度嵌套递归解析与数据拼装depthLimit(7) 限制
大结果集序列化与网络传输分页 + 字段裁剪(客户端只取所需字段)

基准参考(下游 Spring 服务 P99 = 50ms 时):

查询类型P50P99说明
单字段查询55ms120ms主要是网络往返
3 字段聚合60ms150ms并行下游调用,无叠加
10 字段聚合(无 DataLoader)200ms800msN+1 导致延迟叠加
10 字段聚合(有 DataLoader)70ms180ms批处理显著改善

事件循环保护​

Node.js 单线程事件循环是性能根基:任何阻塞都会拖垮该实例上的全部连接。

阻塞源后果处置
同步 API(readFileSync/execSync)请求路径上全实例停顿禁止出现在请求处理路径,仅允许启动初始化阶段
CPU 密集计算(报表、导出、加解密批量)事件循环延迟飙升worker_threads 或甩回 Spring
大 JSON 序列化(> 10MB)主线程长时间占用分页 / 流式响应
正则回溯(ReDoS)实例被打挂见 安全规则 · 正则安全

CPU 密集任务:worker_threads​

确需在 Node.js 侧执行的 CPU 密集任务,放入 Worker 线程,不占用主事件循环:

// infra/worker/report.worker.ts
import { parentPort, workerData } from 'node:worker_threads';

const result = heavyCompute(workerData);
parentPort?.postMessage(result);
// modules/report/report.service.ts
import { Worker } from 'node:worker_threads';

@Injectable()
export class ReportService {
generate(input: ReportInput): Promise<ReportResult> {
return new Promise((resolve, reject) => {
const worker = new Worker(join(__dirname, 'report.worker.js'), { workerData: input });
worker.once('message', resolve);
worker.once('error', reject);
});
}
}

cluster 与多实例部署​

部署形态多进程策略说明
容器化(推荐)不用 cluster,一容器一进程多副本由容器编排平台调度,滚动更新与故障隔离由平台负责
裸机 / VMnode:cluster 或 PM2 按 CPU 核数起多进程进程级故障自动拉起,端口共享由 cluster 模块处理
注意

容器内使用 cluster 会干扰编排平台的副本调度与资源计量(自动扩缩容按容器指标工作),属于反模式。多实例部署的 WebSocket 前置条件(Sticky Session + Redis Adapter)见上文“水平扩展策略”。

水平扩展策略​

Node.js 服务无状态化后,水平扩展无上限:

扩展维度策略触发条件
实例数按指标自动扩缩容(CPU 70% 或内存 80%)连接数或消息量增长
连接分布网关 / LB Sticky Session(按客户端 IP 或 Cookie)握手请求固定到同一实例
广播范围Redis adapter 全集群覆盖任意实例发起广播
多实例两大门槛:Sticky Session + Redis Adapter,缺一不可
  • Sticky Session 必须配置:Socket.IO 握手由多次 HTTP 请求组成(polling → upgrade),不做会话亲和时请求被轮询到不同实例,握手直接失败。在网关 / 负载均衡层按客户端 IP 或 Cookie 保持亲和(配置方式见 网关接入 · 多实例 Sticky Session)
  • Redis Adapter 必须配置:连接固定在某实例后,跨实例消息寻址(server.to(room).emit)依赖 Redis Pub/Sub 转发,见 WebSocket · 多实例广播
  • 二者职责不同:Sticky Session 解决“握手与连接归属”,Redis Adapter 解决“跨实例消息可达”

调优清单​

优先级项操作
P0下游调用并行化Promise.all 替代串行 await
P0DataLoader 批处理消除 N+1 查询
P1Redis 缓存热点数据减少重复下游调用
P1Kafka 批量消费eachBatch 替代 eachMessage
P2日志级别调优生产环境 info,高并发采样
P2连接池复用Redis/Kafka 客户端单例
P3事件循环监控eventLoopDelay 指标告警