Skip to content

Node.js 性能分析与调优

1. 性能分析工具

V8 Profiler (内置)

bash
# 生成 CPU Profile
node --prof app.js
# 处理日志
node --prof-process isolate-*.log > processed.txt

Clinic.js (推荐)

bash
npm install -g clinic
clinic doctor -- node app.js
clinic flame -- node app.js  # 生成火焰图
clinic bubbleprof -- node app.js  # 异步分析

0x 火焰图

bash
npm install -g 0x
0x app.js

2. 火焰图分析

如何阅读火焰图

+-----------------------------------+
|          processData             | ← 最宽的栈 = 热点
+-----------------+---------+-------+
|   parseJSON    | validate |  map  |
+-----------------+---------+-------+
|   JSON.parse   |  regex   | Array |
+-----------------+---------+-------+
  • 横轴: 采样占比 (不是时间线!)
  • 纵轴: 调用栈深度
  • 宽度: 该函数占用的 CPU 时间比例

常见问题模式

模式现象原因
高原很宽的平坦区域单个函数耗时过长
长尾深而窄的调用栈过度递归
分散众多小块函数调用开销过大

"平顶" 现象深度解读 🔥

IMPORTANT

火焰图顶部的宽"平顶"说明该函数本身(而非其调用的子函数)消耗了大量 CPU。

┌─────────────────────────────────────────────────────┐
│                  processData (平顶!)                │ ← 这里是瓶颈
├──────────────┬──────────────┬──────────────────────┤
│  parseJSON   │   validate   │         map          │
├──────────────┴──────────────┴──────────────────────┤
│                   JSON.parse                        │
└─────────────────────────────────────────────────────┘

常见平顶原因:

  • 死循环或复杂循环
  • 灾难性正则回溯 (ReDoS)
  • 密集加密/解密计算
  • 大对象 JSON.stringify/parse

2.1 CPU 密集型排查完整流程 🔥

Step 1: 采样 (Sampling)

bash
# 方法 A: Node.js 内置
node --prof app.js
# 生成 isolate-xxx.log

# 方法 B: clinic.js (推荐)
npx clinic flame -- node app.js

# 方法 C: 0x
npx 0x app.js

TIP

生产环境只做短时间采样 (30秒),避免性能影响。

Step 2: 生成火焰图

bash
# 处理 --prof 日志
node --prof-process isolate-*.log > processed.txt

# 使用 Chrome DevTools
# 1. 打开 chrome://inspect
# 2. 点击 "Open dedicated DevTools for Node"
# 3. Performance 面板录制

Step 3: 定位与优化

javascript
// 问题代码
app.get('/heavy', (req, res) => {
    const result = heavySync();  // 阻塞 Event Loop!
    res.json(result);
});

// 解决方案: Worker Threads
const { Worker } = require('worker_threads');

app.get('/heavy', async (req, res) => {
    const result = await runInWorker('./heavy-task.js', req.body);
    res.json(result);
});

function runInWorker(script, data) {
    return new Promise((resolve, reject) => {
        const worker = new Worker(script, { workerData: data });
        worker.on('message', resolve);
        worker.on('error', reject);
    });
}

3. libuv 线程池调优

默认配置

libuv 线程池默认 4 个线程,处理:

  • 文件系统操作 (fs)
  • DNS 查询 (dns.lookup)
  • Crypto
  • Zlib

问题场景

高并发文件操作时,4 个线程可能成为瓶颈。

调优

bash
# 增加线程池大小 (最大 1024)
UV_THREADPOOL_SIZE=64 node app.js

注意事项

  • 更多线程 ≠ 更好性能 (上下文切换开销)
  • 监控线程池使用情况后再调整
  • 使用异步 DNS (dns.resolve 而非 dns.lookup)

4. 常见性能问题

阻塞 Event Loop

javascript
// 错误: 同步操作阻塞
const data = fs.readFileSync('huge.json');

// 正确: 异步 + 流式
const stream = fs.createReadStream('huge.json');

内存泄漏

javascript
// 错误: 全局缓存无限增长
const cache = {};
app.get('/user/:id', (req, res) => {
    cache[req.params.id] = fetchUser(req.params.id); // 永不清理
});

// 正确: 使用 LRU 缓存
const LRU = require('lru-cache');
const cache = new LRU({ max: 500 });

未优化的正则

javascript
// 危险: 灾难性回溯 (ReDoS)
const evilRegex = /^(a+)+$/;
evilRegex.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaa!'); // 卡死

// 修复: 简化正则
const safeRegex = /^a+$/;

5. 监控指标

指标获取方式警戒值
Event Loop Lagperf_hooks> 100ms
Heap Usedprocess.memoryUsage()持续上升
Active Handlesprocess._getActiveHandles()异常增长
CPU Usageos.cpus()> 80%

Event Loop Lag 监控

javascript
const { monitorEventLoopDelay } = require('perf_hooks');

const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
    console.log(`Event Loop Lag: min=${h.min}ms, max=${h.max}ms, mean=${h.mean}ms`);
    h.reset();
}, 5000);

前端面试知识库