性能调优
What — 是什么
性能调优是通过压测、profiling 和监控定位 Node.js 应用的性能瓶颈,并进行针对性优化的过程。
核心概念:
- 压力测试:用 autocannon/k6/artillery 模拟高并发请求,测量 QPS/延迟/P99
- 内存泄漏:
process.memoryUsage()监控 + heapdump 定位持续增长的对象 - 事件循环延迟:
perf_hooks.monitorEventLoopDelay检测主线程阻塞 - 火焰图:
--prof+--prof-process或 clinic.js 生成 CPU 火焰图 - profiling:V8 的 CPU/内存性能分析,
node --inspect+ Chrome DevTools - Cluster 调优:多进程 + 连接池 + 负载均衡
关键特性:
- 事件循环延迟 > 100ms 说明有阻塞操作
- 火焰图可视化展示 CPU 时间花在哪里
clinic doctor自动诊断性能问题
Why — 为什么
适用场景:
- API 响应慢(P99 超过 1 秒)
- 内存持续增长疑似泄漏
- 高并发下 QPS 上不去
- 启动时间长
How — 怎么用
代码示例
# 压力测试
npx autocannon -c 100 -d 30 http://localhost:3000/api/users
# CPU profiling
node --prof app.js
node --prof-process isolate-*.log > profile.txt
# clinic.js 诊断
npx clinic doctor -- node app.js
# 生成 HTML 报告
# 事件循环延迟监控
const { monitorEventLoopDelay } = require('perf_hooks');
const histogram = monitorEventLoopDelay({ resolution: 20 });
histogram.enable();
setInterval(() => {
if (histogram.percentile(99) > 100) {
console.warn('事件循环延迟过高:', histogram.percentile(99));
}
}, 5000);
// 内存监控
setInterval(() => {
const { heapUsed, rss } = process.memoryUsage();
if (heapUsed > 500 * 1024 * 1024) {
const v8 = require('v8');
v8.getHeapSnapshot().pipe(require('fs').createWriteStream(`heap-${Date.now()}.heapsnapshot`));
}
}, 30000);
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| P99 延迟高 | 少量慢请求拉高 | 排查慢查询/外部 API 超时 |
| QPS 上不去 | 单进程瓶颈 | Cluster 模式 + 连接池 |
| 内存持续增长 | 事件监听器/闭包泄漏 | heapdump 对比分析 |
| 启动慢 | 大量同步 require | 延迟加载/ESM lazy import |
最佳实践
- 上线前压测,确定 QPS 基线和 P99 延迟
- 监控事件循环延迟和内存使用
- 用 clinic.js 自动诊断性能问题
- 数据库查询加索引 + 连接池调优
- Cluster 模式利用多核
面试题
Q1: Node.js 性能调优的常见手段?
① Cluster 多进程——利用多核提升吞吐;② 连接池——复用数据库/HTTP 连接;③ 缓存——Redis 缓存热点数据;④ 流式处理——Stream 处理大数据;⑤ 异步化——CPU 密集任务 offload 到 Worker Threads;⑥ 事件循环监控——检测阻塞并拆分任务;⑦ 压缩——gzip/brotli 减少网络传输;⑧ 索引——数据库查询优化。
Q2: 如何定位 Node.js 内存泄漏?
步骤:①
process.memoryUsage()确认 heapUsed 持续增长;② 两个时间点生成 heapdump(v8.getHeapSnapshot());③ Chrome DevTools Memory 面板加载两个快照;④ Comparison 视图找到增长的构造函数;⑤ 查看 Retainers 追踪引用链;⑥ 定位代码中持有引用的对象(未移除的事件监听器/全局 Map/闭包引用)。
相关链接: