引言
Node.js 以其事件驱动和非阻塞 I/O 模型,成为构建高并发应用的首选。然而,随着业务复杂度提升,性能问题逐渐暴露:响应变慢、CPU 飙升、内存泄漏……如何系统性地进行性能调优?本文将从诊断工具开始,带你一步步定位和解决常见的性能瓶颈。
1. 性能诊断:先测量,再优化
盲目优化是浪费时间。我们需要先用工具量化问题。
1.1 内置工具:--prof 和 --trace-gc
Node.js 内置了 V8 分析器,可以生成 CPU profile 和 GC 追踪。
node --prof app.js
# 运行一段时间后,会生成 isolate-0x...-v8.log 文件
node --prof-process isolate-*.log > processed.txt
查看 processed.txt,重点关注 ticks 占比高的函数。
1.2 使用 Clinic.js 快速定位
Clinic.js 是一套可视化诊断工具,特别适合快速上手。
npm install -g clinic
clinic doctor -- node app.js
它会自动生成报告,指出 CPU、内存或 I/O 瓶颈。
1.3 火焰图:直观展示调用栈
使用 0x 工具生成火焰图:
npm install -g 0x
0x app.js
火焰图能让你一眼看出哪个函数占用 CPU 最多。
2. 优化 CPU 密集型任务
2.1 问题场景:同步 JSON 解析
假设我们有一个 HTTP 服务,需要解析大型 JSON 数据:
const http = require('http');
const server = http.createServer((req, res) => {
// 模拟接收大型 JSON 字符串
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const data = JSON.parse(body); // 同步解析,阻塞事件循环
res.end(JSON.stringify({ size: data.length }));
});
});
server.listen(3000);
当 JSON 很大时,JSON.parse 会阻塞事件循环,导致其他请求无法处理。
2.2 解决方案:使用 worker_threads
worker_threads 模块可以将计算密集型任务放到独立线程中。
// 主进程
const { Worker } = require('worker_threads');
function parseJSONAsync(jsonString) {
return new Promise((resolve, reject) => {
const worker = new Worker(`
const { parentPort, workerData } = require('worker_threads');
try {
const data = JSON.parse(workerData);
parentPort.postMessage(data);
} catch (err) {
parentPort.postMessage({ error: err.message });
}
`, { eval: true, workerData: jsonString });
worker.once('message', resolve);
worker.once('error', reject);
});
}
// 使用
http.createServer(async (req, res) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
const data = await parseJSONAsync(body);
res.end(JSON.stringify({ size: data.length }));
});
});
💡 注意:不要为每个请求创建新 worker,最好使用 worker 池(如 piscina 库)。
2.3 其他优化:使用更快的库
- 用
fast-json-parse替代JSON.parse,它不会抛异常,性能更好。 - 使用
JSON.parse的替代品json-bigint处理大数。
3. 内存泄漏定位与修复
3.1 症状
内存占用持续增长,最终导致进程崩溃(OOM)。
3.2 诊断:使用 heapdump 和 --inspect
npm install heapdump
在代码中手动触发堆转储:
const heapdump = require('heapdump');
// 在可疑位置
heapdump.writeSnapshot((err, filename) => {
console.log('Heap dump written to', filename);
});
然后使用 Chrome DevTools 的 Memory 面板分析堆快照。
3.3 常见泄漏点:闭包和全局变量
// 泄漏示例
const cache = {};
function processRequest(id) {
cache[id] = { data: new Array(10000).join('x') }; // 一直往全局对象塞数据
}
修复:使用 WeakMap 或定期清理。
const cache = new WeakMap();
3.4 使用 --max-old-space-size 调整堆大小
node --max-old-space-size=4096 app.js
但调整大小只是治标,根本是找到泄漏点。
4. I/O 优化:提升并发处理能力
4.1 使用 Promise.all 并发执行 I/O
// 串行
const result1 = await db.query('SELECT ...');
const result2 = await db.query('SELECT ...');
// 并行
const [result1, result2] = await Promise.all([
db.query('SELECT ...'),
db.query('SELECT ...')
]);
4.2 控制并发数:使用 p-limit
如果并发太多,可能压垮数据库。使用 p-limit 控制:
const pLimit = require('p-limit');
const limit = pLimit(10); // 最多 10 个并发
const tasks = urls.map(url => limit(() => fetch(url)));
const results = await Promise.all(tasks);
4.3 流式处理大文件
使用 stream 而不是一次性读取整个文件:
const fs = require('fs');
const readline = require('readline');
const rl = readline.createInterface({
input: fs.createReadStream('large.log'),
crlfDelay: Infinity
});
rl.on('line', (line) => {
// 处理每一行
});
5. 使用缓存减少重复计算
5.1 内存缓存:node-cache
const NodeCache = require('node-cache');
const myCache = new NodeCache({ stdTTL: 600 }); // 10分钟
function getData(key) {
let value = myCache.get(key);
if (value === undefined) {
value = expensiveOperation(key);
myCache.set(key, value);
}
return value;
}
5.2 分布式缓存:Redis
const redis = require('redis');
const client = redis.createClient();
async function getData(key) {
let value = await client.get(key);
if (value === null) {
value = await expensiveOperation(key);
await client.set(key, value, { EX: 600 });
}
return value;
}
6. 使用集群充分利用多核 CPU
Node.js 单进程只能用一个 CPU 核心,通过 cluster 模块可以启动多个进程。
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numWorkers = os.cpus().length;
for (let i = 0; i < numWorkers; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log('Worker died, restarting...');
cluster.fork();
});
} else {
// 启动应用
const http = require('http');
http.createServer((req, res) => {
res.end('Hello World');
}).listen(3000);
}
💡 提示:生产环境建议使用 PM2 管理进程,它内置了集群和负载均衡。
7. 性能监控和预警
7.1 使用 process 事件监控内存
const memoryUsage = process.memoryUsage();
console.log(`RSS: ${memoryUsage.rss / 1024 / 1024} MB`);
7.2 集成 APM 工具
- New Relic
- Datadog
- 阿里云 ARMS
这些工具能实时监控应用性能并报警。
8. 常见坑与最佳实践
8.1 不要阻塞事件循环
避免在回调中执行同步 CPU 密集操作,如 crypto 的同步方法。
8.2 使用 async 库控制流程
老生常谈,但 async 库的 eachLimit 等函数仍然好用。
8.3 设置 NODE_ENV=production
生产环境会启用 V8 的优化,性能提升明显。
8.4 使用 --max-http-header-size 调整请求头大小
如果请求头很大,默认 8KB 可能不够。
总结
性能调优是一个系统性的工程,需要先测量再优化。本文从诊断工具入手,覆盖了 CPU 密集、内存泄漏、I/O 并发和集群部署等核心场景。记住:优化要基于数据,不要过度设计。
下一步,你可以深入学习 V8 引擎的优化机制,或者尝试使用 bun 等新兴运行时。希望本文能帮助你构建更高性能的 Node.js 应用。