Node.js 性能优化实战:从基础到进阶
引言
Node.js 以其高性能和异步 I/O 特性而闻名,但在实际应用中,如果不注意性能优化,仍然会遇到各种性能问题。本文将从基础到进阶,系统地介绍 Node.js 性能优化的各种技巧和最佳实践。
一、事件循环优化
1.1 避免阻塞事件循环
Node.js 是单线程模型,任何阻塞操作都会影响整个应用的性能。
// 错误示例:同步文件读取
const fs = require('fs');
const data = fs.readFileSync('/path/to/file'); // 阻塞!
// 正确做法:异步读取
fs.readFile('/path/to/file', (err, data) => {
if (err) throw err;
console.log(data);
});
// 更好的做法:使用 Promise
const fsPromises = require('fs').promises;
async function readFileAsync() {
try {
const data = await fsPromises.readFile('/path/to/file');
console.log(data);
} catch (err) {
console.error(err);
}
}
1.2 合理使用 setImmediate 和 process.nextTick
// setImmediate: 在 I/O 事件之后执行
setImmediate(() => {
console.log('在 I/O 之后执行');
});
// process.nextTick: 在当前操作完成后立即执行
process.nextTick(() => {
console.log('立即执行');
});
二、内存管理
2.1 避免内存泄漏
常见的内存泄漏场景:
// 1. 全局变量累积
const cache = {};
function addToCache(key, value) {
cache[key] = value; // 没有清理机制,会一直增长
}
// 解决方案:使用 LRU 缓存
const LRU = require('lru-cache');
const cache = new LRU({
max: 500,
maxAge: 1000 * 60 * 60 // 1小时
});
// 2. 事件监听器没有移除
const EventEmitter = require('events');
const emitter = new EventEmitter();
function setupListener() {
emitter.on('event', handler); // 如果不移除,会一直占用内存
}
// 正确做法
function cleanup() {
emitter.removeListener('event', handler);
}
2.2 使用 Stream 处理大文件
// 错误:一次性读取整个文件
const fs = require('fs');
fs.readFile('large-file.txt', (err, data) => {
// 大文件会占用大量内存
});
// 正确:使用 Stream
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
readStream.on('data', (chunk) => {
console.log(`读取 ${chunk.length} 字节`);
});
三、数据库优化
3.1 连接池管理
const mysql = require('mysql2/promise');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
database: 'test',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// 使用连接池
async function query(sql) {
const [rows] = await pool.execute(sql);
return rows;
}
3.2 查询优化
// 使用索引
// CREATE INDEX idx_user_email ON users(email);
// 批量插入
const values = [
[1, 'user1'],
[2, 'user2'],
[3, 'user3']
];
await pool.query(
'INSERT INTO users (id, name) VALUES ?',
[values]
);
// 使用 SELECT 指定字段,而不是 SELECT *
const [users] = await pool.execute(
'SELECT id, name, email FROM users WHERE status = ?',
['active']
);
四、缓存策略
4.1 内存缓存
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 600 });
async function getUserData(userId) {
// 先查缓存
const cached = cache.get(`user_${userId}`);
if (cached) {
return cached;
}
// 缓存未命中,查数据库
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
// 存入缓存
cache.set(`user_${userId}`, user);
return user;
}
4.2 Redis 缓存
const redis = require('redis');
const client = redis.createClient();
async function cacheData(key, data, ttl = 3600) {
await client.setEx(key, ttl, JSON.stringify(data));
}
async function getCachedData(key) {
const data = await client.get(key);
return data ? JSON.parse(data) : null;
}
五、并发控制
5.1 使用 Worker Threads
const { Worker } = require('worker_threads');
function runWorker(workerData) {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker.js', { workerData });
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0) {
reject(new Error(`Worker stopped with exit code ${code}`));
}
});
});
}
// 使用
async function processData(data) {
const result = await runWorker(data);
return result;
}
5.2 限制并发数
const pLimit = require('p-limit');
const limit = pLimit(5); // 最多 5 个并发
const tasks = urls.map(url =>
limit(() => fetch(url))
);
const results = await Promise.all(tasks);
六、监控和调试
6.1 使用 Clinic.js
# 安装
npm install -g clinic
# 运行诊断
clinic doctor -- node app.js
clinic flame -- node app.js
clinic bubbleprof -- node app.js
6.2 性能监控
const { performance } = require('perf_hooks');
function measurePerformance(fn, label) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${label}: ${end - start}ms`);
return result;
}
// 使用
measurePerformance(() => {
// 你的代码
}, '操作耗时');
总结
Node.js 性能优化是一个系统工程,需要从多个方面入手:
- 事件循环优化:避免阻塞操作
- 内存管理:防止内存泄漏,合理使用 Stream
- 数据库优化:使用连接池和查询优化
- 缓存策略:合理使用内存和 Redis 缓存
- 并发控制:使用 Worker Threads 和限制并发数
- 监控调试:使用专业工具进行性能分析
通过这些优化手段,可以显著提升 Node.js 应用的性能和稳定性。