第 21 章 · 性能优化
第 21 章 · 性能优化
21.1 性能优化概述
性能瓶颈分类
| 瓶颈类型 | 表现 | 解决方案 |
|---|---|---|
| I/O 瓶颈 | 数据库慢、网络延迟 | 缓存、连接池、异步 |
| CPU 瓶颈 | 计算密集、事件循环阻塞 | Worker Threads、Cluster |
| 内存瓶颈 | 内存泄漏、OOM 崩溃 | 内存分析、优化数据结构 |
| 网络瓶颈 | 响应慢、带宽不足 | 压缩、CDN、分页 |
21.2 Cluster 模块
Node.js 单线程只能利用一个 CPU 核心。Cluster 模块可以创建多个工作进程,充分利用多核 CPU。
const cluster = require('cluster');
const http = require('http');
const os = require('os');
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`主进程 ${process.pid} 正在运行`);
console.log(`启动 ${numCPUs} 个工作进程`);
// Fork 工作进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 工作进程退出时重启
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 退出 (${signal || code}),重启中...`);
cluster.fork();
});
// 优雅重启
process.on('SIGUSR2', () => {
const workers = Object.values(cluster.workers);
function restartWorker(i) {
if (i >= workers.length) return;
const worker = workers[i];
console.log(`重启工作进程 ${worker.process.pid}`);
worker.disconnect();
worker.on('disconnect', () => {
const newWorker = cluster.fork();
newWorker.on('listening', () => restartWorker(i + 1));
});
}
restartWorker(0);
});
} else {
const app = require('./app');
const server = app.listen(3000, () => {
console.log(`工作进程 ${process.pid} 监听端口 3000`);
});
}
使用 pm2 管理 Cluster(推荐)
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: 'src/server.js',
instances: 'max', // 使用所有 CPU 核心
exec_mode: 'cluster',
max_memory_restart: '1G',
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
}],
};
# 启动
pm2 start ecosystem.config.js --env production
# 管理
pm2 status
pm2 logs
pm2 reload all # 零停机重启
pm2 stop all
pm2 delete all
21.3 Worker Threads
适用于 CPU 密集型任务,不会阻塞主事件循环。
// worker-pool.js
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if (isMainThread) {
// 主线程
function runTask(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker exited with code ${code}`));
});
});
}
// 并行执行 CPU 密集型任务
async function main() {
const start = Date.now();
const results = await Promise.all([
runTask({ start: 0, end: 1e7 }),
runTask({ start: 1e7, end: 2e7 }),
runTask({ start: 2e7, end: 3e7 }),
runTask({ start: 3e7, end: 4e7 }),
]);
const total = results.reduce((a, b) => a + b, 0);
console.log(`结果: ${total}, 耗时: ${Date.now() - start}ms`);
}
main();
} else {
// 工作线程
function heavyComputation(start, end) {
let sum = 0;
for (let i = start; i < end; i++) {
sum += Math.sqrt(i);
}
return sum;
}
const result = heavyComputation(workerData.start, workerData.end);
parentPort.postMessage(result);
}
Worker Threads 线程池
const { Worker } = require('worker_threads');
const os = require('os');
class ThreadPool {
constructor(workerFile, poolSize = os.cpus().length) {
this.workerFile = workerFile;
this.pool = [];
this.queue = [];
for (let i = 0; i < poolSize; i++) {
this.pool.push({ worker: new Worker(workerFile), busy: false });
}
}
run(task) {
return new Promise((resolve, reject) => {
const available = this.pool.find(w => !w.busy);
if (available) {
this._execute(available, task, resolve, reject);
} else {
this.queue.push({ task, resolve, reject });
}
});
}
_execute(wrapper, task, resolve, reject) {
wrapper.busy = true;
const worker = wrapper.worker;
worker.postMessage(task);
worker.once('message', (result) => {
resolve(result);
wrapper.busy = false;
if (this.queue.length > 0) {
const { task, resolve, reject } = this.queue.shift();
this._execute(wrapper, task, resolve, reject);
}
});
worker.once('error', reject);
}
async shutdown() {
await Promise.all(this.pool.map(w => w.worker.terminate()));
}
}
21.4 内存分析
监控内存使用
// 内存使用监控
function logMemoryUsage() {
const mem = process.memoryUsage();
console.log({
rss: `${Math.round(mem.rss / 1024 / 1024)}MB`, // 进程总内存
heapTotal: `${Math.round(mem.heapTotal / 1024 / 1024)}MB`, // 堆总量
heapUsed: `${Math.round(mem.heapUsed / 1024 / 1024)}MB`, // 堆已用
external: `${Math.round(mem.external / 1024 / 1024)}MB`, // C++ 对象
arrayBuffers: `${Math.round(mem.arrayBuffers / 1024 / 1024)}MB`,
});
}
setInterval(logMemoryUsage, 30000); // 每 30 秒记录
生成 Heap Snapshot
const v8 = require('v8');
const fs = require('fs');
// 手动生成堆快照
function takeHeapSnapshot() {
const snapshotStream = v8.writeHeapSnapshot();
console.log(`堆快照已保存: ${snapshotStream}`);
// 用 Chrome DevTools 打开 .heapsnapshot 文件分析
}
// 监控内存,超过阈值时自动采集
const MAX_HEAP = 500 * 1024 * 1024; // 500MB
setInterval(() => {
if (process.memoryUsage().heapUsed > MAX_HEAP) {
takeHeapSnapshot();
}
}, 10000);
# 使用 --inspect 启动后,Chrome DevTools Memory 面板可以:
# 1. 拍摄堆快照(Heap Snapshot)
# 2. 记录内存分配时间线(Allocation Timeline)
# 3. 记录内存分配采样(Allocation Sampling)
常见内存泄漏
// ❌ 泄漏 1:无限制的缓存
const cache = {};
function addToCache(key, value) {
cache[key] = value; // 永远不会被清理
}
// ✅ 使用 LRU 缓存
const { LRUCache } = require('lru-cache');
const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 5 });
// ❌ 泄漏 2:未移除的事件监听器
class LeakyEmitter extends EventEmitter {
constructor() {
super();
// 每次创建实例都会增加监听器
setInterval(() => {
this.emit('data', Date.now());
}, 1000);
}
}
// ❌ 泄漏 3:闭包持有大对象
function processData() {
const largeData = new Array(1000000).fill('x');
return function getLength() {
return largeData.length; // largeData 被闭包持有,无法回收
};
}
// ✅ 只保留需要的值
function processData() {
const largeData = new Array(1000000).fill('x');
const length = largeData.length;
return function getLength() {
return length;
};
}
21.5 CPU Profiling
# 方式 1:命令行
node --prof app.js
node --prof-process isolate-*.log > processed.txt
# 方式 2:使用 --inspect + Chrome DevTools
node --inspect app.js
# 打开 chrome://inspect → Profiler → 开始录制
# 方式 3:使用 perf_hooks
node --cpu-prof app.js
// 使用 perf_hooks 进行精确计时
const { performance, PerformanceObserver } = require('perf_hooks');
const obs = new PerformanceObserver((items) => {
items.getEntries().forEach((entry) => {
console.log(`${entry.name}: ${entry.duration.toFixed(3)}ms`);
});
});
obs.observe({ entryTypes: ['measure'] });
function benchmark(name, fn, iterations = 1000) {
performance.mark(`${name}-start`);
for (let i = 0; i < iterations; i++) fn();
performance.mark(`${name}-end`);
performance.measure(name, `${name}-start`, `${name}-end`);
}
benchmark('JSON.parse', () => JSON.parse('{"a":1}'));
benchmark('正则匹配', () => /test/.test('this is a test'));
21.6 通用优化技巧
数据库优化
// ✅ 使用连接池
const pool = mysql.createPool({ connectionLimit: 20 });
// ✅ 批量查询代替循环查询
// ❌ N+1 问题
for (const order of orders) {
order.user = await db.users.findById(order.userId);
}
// ✅ 一次查询
const userIds = orders.map(o => o.userId);
const users = await db.users.find({ id: { $in: userIds } });
const userMap = new Map(users.map(u => [u.id, u]));
orders.forEach(o => o.user = userMap.get(o.userId));
// ✅ 只查询需要的字段
const users = await prisma.user.findMany({
select: { id: true, name: true }, // 不要 select *
});
// ✅ 添加索引
// 频繁 WHERE 的字段添加索引
缓存策略
const { createClient } = require('redis');
const redis = createClient();
async function getCachedUser(id) {
const cacheKey = `user:${id}`;
// 先查缓存
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
// 缓存未命中,查数据库
const user = await db.users.findById(id);
if (user) {
await redis.setEx(cacheKey, 300, JSON.stringify(user)); // 缓存 5 分钟
}
return user;
}
注意事项
⚠️ 先测量再优化:不要盲目优化,使用 Profiler 找到真正的瓶颈。
⚠️ Cluster 不适合 CPU 密集型任务:Cluster 适合 I/O 密集型 Web 服务,CPU 密集型应使用 Worker Threads。
⚠️ 注意内存泄漏:定期检查内存使用,使用
--max-old-space-size限制堆大小。
业务场景
- 高并发 API:Cluster 模式充分利用多核 CPU
- 数据处理:Worker Threads 并行处理大量数据
- 图片处理:使用 Worker Threads 避免阻塞事件循环
- 缓存层:Redis 缓存热点数据,减少数据库压力
扩展阅读
- Node.js Cluster 文档
- Node.js Worker Threads 文档
- Node.js 调试指南
- clinic.js — Node.js 性能分析工具
上一章:第 20 章 · 安全 下一章:第 22 章 · Docker 部署 — Dockerfile、多阶段构建和 PM2。