Node.js 性能优化实战

Node.js 性能优化实战

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
// ❌ 阻塞式 I/O
const fs = require('fs');
const data = fs.readFileSync('large-file.txt'); // 阻塞

// ✅ 异步 I/O
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; // 永不清理
}

// ✅ 使用 LRU 缓存
const LRU = require('lru-cache');
const cache = new LRU({
max: 1000, // 最大条目数
maxAge: 1000 * 60 * 60 // 1小时过期
});

二、异步编程优化

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/await
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);
});

// ✅ API 错误处理
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();
// 使用 buffer
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]);

// ✅ 使用 LIMIT 限制结果
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 }); // 10分钟过期

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`);

// Fork workers
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 {
// Worker 进程
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
// 安装 PM2
// npm install pm2 -g

// 启动应用
// pm2 start app.js -i max

// ecosystem.config.js
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
// New Relic
require('newrelic');

// Datadog
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
// ❌ 数组查找 - O(n)
function findUser(users, id) {
return users.find(user => user.id === id);
}

// ✅ Map 查找 - O(1)
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 持续优化

  • 建立性能基准
  • 定期性能测试
  • 根据监控数据优化
  • 保持依赖更新

性能优化是一个持续的过程,需要不断地监控、分析和改进。