Node.js 性能优化实战

Node.js 以其高性能和可扩展性而闻名,但要让应用达到最佳性能,需要深入理解其工作原理并采用适当的优化策略。本文将从多个维度介绍 Node.js 性能优化的实战技巧。
一、理解 Node.js 的性能瓶颈
1.1 单线程限制
Node.js 是单线程的,这意味着 CPU 密集型任务会阻塞事件循环:
1 2 3 4 5 6 7 8 9 10 11 12
| function heavyComputation() { for (let i = 0; i < 1e9; i++) { Math.sqrt(i); } }
app.get('/compute', (req, res) => { heavyComputation(); res.send('Done'); });
|
1.2 I/O 操作
虽然是异步的,但不当的处理仍会导致性能问题:
1 2 3 4 5 6 7 8 9
| const fs = require('fs'); const data = fs.readFileSync('large-file.txt');
fs.readFile('large-file.txt', (err, data) => { if (err) throw err; console.log(data); });
|
1.3 内存泄漏
不当的对象引用会导致内存无法被垃圾回收:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const cache = {};
function addToCache(key, value) { cache[key] = value; }
const LRU = require('lru-cache'); const cache = new LRU({ max: 1000, maxAge: 1000 * 60 * 60 });
|
二、异步编程优化
2.1 避免回调地狱
使用 async/await 使代码更清晰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function getUserData(userId, callback) { getUser(userId, (user) => { getPosts(user.id, (posts) => { getComments(posts[0].id, (comments) => { callback({ user, posts, comments }); }); }); }); }
async function getUserData(userId) { const user = await getUser(userId); const posts = await getPosts(user.id); const comments = await getComments(posts[0].id); return { user, posts, comments }; }
|
2.2 并行处理
使用 Promise.all 并行执行独立任务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| async function processItems(items) { const results = []; for (const item of items) { const result = await processItem(item); results.push(result); } return results; }
async function processItems(items) { const promises = items.map(item => processItem(item)); return await Promise.all(promises); }
|
2.3 错误处理
合理的错误处理避免进程崩溃:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| process.on('uncaughtException', (err) => { console.error('Uncaught Exception:', err); process.exit(1); });
process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection at:', promise, 'reason:', reason); });
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something went wrong!'); });
|
三、内存管理
3.1 监控内存使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| app.use((req, res, next) => { const used = process.memoryUsage(); for (let key in used) { console.log(`${key} ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`); } next(); });
setInterval(() => { const usage = process.memoryUsage(); console.log('Memory Usage:', { rss: `${(usage.rss / 1024 / 1024).toFixed(2)} MB`, heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`, heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`, external: `${(usage.external / 1024 / 1024).toFixed(2)} MB` }); }, 60000);
|
3.2 流式处理大文件
避免一次性加载大文件到内存:
1 2 3 4 5 6 7 8 9 10 11
| app.get('/download', (req, res) => { const data = fs.readFileSync('large-file.txt'); res.send(data); });
app.get('/download', (req, res) => { const stream = fs.createReadStream('large-file.txt'); stream.pipe(res); });
|
3.3 对象池
重用对象减少 GC 压力:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class ObjectPool { constructor(createFn, resetFn, initialSize = 10) { this.createFn = createFn; this.resetFn = resetFn; this.pool = []; for (let i = 0; i < initialSize; i++) { this.pool.push(this.createFn()); } } acquire() { return this.pool.length > 0 ? this.pool.pop() : this.createFn(); } release(obj) { this.resetFn(obj); this.pool.push(obj); } }
const bufferPool = new ObjectPool( () => Buffer.alloc(1024), (buf) => buf.fill(0) );
const buffer = bufferPool.acquire();
bufferPool.release(buffer);
|
四、数据库优化
4.1 连接池
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const mysql = require('mysql2/promise');
const pool = mysql.createPool({ host: 'localhost', user: 'root', password: 'password', database: 'mydb', waitForConnections: true, connectionLimit: 10, queueLimit: 0 });
async function query(sql, params) { const connection = await pool.getConnection(); try { const [rows] = await connection.execute(sql, params); return rows; } finally { connection.release(); } }
|
4.2 批量操作
1 2 3 4 5 6 7 8 9 10 11 12 13
| async function insertUsers(users) { for (const user of users) { await db.query('INSERT INTO users SET ?', user); } }
async function insertUsers(users) { const sql = 'INSERT INTO users (name, email) VALUES ?'; const values = users.map(u => [u.name, u.email]); await db.query(sql, [values]); }
|
4.3 查询优化
1 2 3 4 5 6 7 8
| db.query('SELECT * FROM users WHERE email = ?', [email]);
db.query('SELECT id, name FROM users WHERE id = ?', [id]);
db.query('SELECT * FROM posts LIMIT 10 OFFSET ?', [offset]);
|
五、缓存策略
5.1 内存缓存
1 2 3 4 5 6 7 8 9 10 11 12 13
| const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 600 });
async function getUser(id) { const cached = cache.get(id); if (cached) { return cached; } const user = await db.query('SELECT * FROM users WHERE id = ?', [id]); cache.set(id, user); return user; }
|
5.2 Redis 缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const redis = require('redis'); const client = redis.createClient();
async function getUser(id) { const cached = await client.get(`user:${id}`); if (cached) { return JSON.parse(cached); } const user = await db.query('SELECT * FROM users WHERE id = ?', [id]); await client.setex(`user:${id}`, 3600, JSON.stringify(user)); return user; }
|
5.3 缓存失效
1 2 3 4 5 6 7 8
| async function updateUser(id, data) { await db.query('UPDATE users SET ? WHERE id = ?', [data, id]); await client.del(`user:${id}`); await client.del(`user:posts:${id}`); }
|
六、集群与负载均衡
6.1 使用 Cluster 模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const cluster = require('cluster'); const numCPUs = require('os').cpus().length;
if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); for (let i = 0; i < numCPUs; i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`Worker ${worker.process.pid} died`); cluster.fork(); }); } else { const express = require('express'); const app = express(); app.get('/', (req, res) => { res.send(`Hello from worker ${process.pid}`); }); app.listen(3000, () => { console.log(`Worker ${process.pid} started`); }); }
|
6.2 PM2 进程管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
module.exports = { apps: [{ name: 'my-app', script: './app.js', instances: 'max', exec_mode: 'cluster', max_memory_restart: '1G', error_file: './logs/error.log', out_file: './logs/out.log', log_date_format: 'YYYY-MM-DD HH:mm:ss Z', merge_logs: true, env: { NODE_ENV: 'production' } }] };
|
七、HTTP 优化
7.1 压缩响应
1 2
| const compression = require('compression'); app.use(compression());
|
7.2 启用 HTTP/2
1 2 3 4 5 6 7 8 9
| const http2 = require('http2'); const fs = require('fs');
const server = http2.createSecureServer({ key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }, app);
server.listen(443);
|
7.3 使用 CDN
1 2 3 4 5 6 7 8
| app.use(express.static('public', { maxAge: '1y', setHeaders: (res, path) => { if (path.endsWith('.html')) { res.setHeader('Cache-Control', 'no-cache'); } } }));
|
八、性能监控
8.1 使用 APM 工具
1 2 3 4 5
| require('newrelic');
const tracer = require('dd-trace').init();
|
8.2 自定义监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const promClient = require('prom-client');
const httpRequestDuration = new promClient.Histogram({ name: 'http_request_duration_seconds', help: 'Duration of HTTP requests in seconds', labelNames: ['method', 'route', 'status_code'] });
app.use((req, res, next) => { const end = httpRequestDuration.startTimer(); res.on('finish', () => { end({ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode }); }); next(); });
app.get('/metrics', async (req, res) => { res.set('Content-Type', promClient.register.contentType); res.end(await promClient.register.metrics()); });
|
九、代码优化技巧
9.1 避免不必要的计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function process(items) { return items.map(item => { const result = expensiveComputation(item); return result; }); }
const memoized = new Map();
function expensiveComputation(item) { const key = JSON.stringify(item); if (memoized.has(key)) { return memoized.get(key); } const result = doCompute(item); memoized.set(key, result); return result; }
|
9.2 使用更快的数据结构
1 2 3 4 5 6 7 8 9 10 11 12
| function findUser(users, id) { return users.find(user => user.id === id); }
const userMap = new Map(); users.forEach(user => userMap.set(user.id, user));
function findUser(id) { return userMap.get(id); }
|
十、最佳实践总结
10.1 编码规范
- 使用 async/await 替代回调
- 错误处理要完善
- 避免阻塞事件循环
- 合理使用缓存
10.2 监控与日志
- 记录关键指标
- 设置告警机制
- 定期性能分析
- 日志分级记录
10.3 持续优化
- 建立性能基准
- 定期性能测试
- 根据监控数据优化
- 保持依赖更新
性能优化是一个持续的过程,需要不断地监控、分析和改进。