Node.js 性能优化

性能优化是应用上线后的持续工作。Node.js 应用可能会遇到 CPU 瓶颈、内存泄漏、响应缓慢等问题。本章将系统性地介绍从代码层面到架构层面的优化策略,帮助你提升 Node.js 应用的吞吐量和响应速度。

1. 代码层优化

编写高效的 JavaScript 代码是性能优化的基础。

1.1 使用异步非阻塞 I/O

Node.js 的优势在于异步 I/O,确保所有 I/O 操作都使用异步版本,避免阻塞事件循环。例如使用 fs.promises 代替 fs.readFileSync

// 不要这样做
const data = fs.readFileSync('file.txt'); // 阻塞

// 应该这样做
const data = await fs.promises.readFile('file.txt');

1.2 避免同步函数

除了文件 I/O,还有其它同步 API(如 crypto.randomBytesSync),在服务器初始化之外应避免使用。

1.3 优化循环和算法

使用高效的算法和数据结构,避免不必要的计算。例如使用 for 循环代替 forEach 在性能敏感的场景,不过通常差异很小。

// 优化前
const result = arr.map(x => x * 2).filter(x => x > 10).reduce((acc, x) => acc + x, 0);

// 如果数组很大,考虑一次循环完成
let sum = 0;
for (const x of arr) {
  const doubled = x * 2;
  if (doubled > 10) sum += doubled;
}

1.4 使用原生方法

原生方法如 JSON.parseArray.prototype 方法通常比自定义实现更快。

1.5 避免内存泄漏

全局变量、未清理的监听器、闭包引用等会导致内存泄漏。使用 WeakMap/WeakSet 处理缓存,及时移除事件监听器。

2. 并发与负载均衡

Node.js 单线程模型下,利用多核 CPU 需要集群模式。

2.1 使用 Node.js 原生集群

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
  const numCPUs = os.cpus().length;
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
  cluster.on('exit', (worker) => {
    console.log(`工作进程 ${worker.process.pid} 退出,重启新进程`);
    cluster.fork();
  });
} else {
  // 工作进程启动应用服务器
  require('./app');
}

2.2 使用 PM2 管理集群

PM2 内置负载均衡,只需一行命令即可启动集群模式:

pm2 start app.js -i max

2.3 使用反向代理(Nginx)

Nginx 位于 Node.js 之前,可以处理静态文件、负载均衡和 SSL 终止。配置示例:

upstream node_app {
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}
server {
    listen 80;
    location / {
        proxy_pass http://node_app;
    }
}

3. 内存管理

内存泄漏会导致性能下降和进程崩溃。定期监控内存使用。

3.1 使用堆快照分析

使用 heapdumpv8-profiler 生成堆快照,在 Chrome DevTools 中分析。

const heapdump = require('heapdump');
heapdump.writeSnapshot((err, filename) => {
  console.log('堆快照已写入', filename);
});

3.2 避免常见内存泄漏场景

  • 全局变量:避免意外创建全局变量。
  • 闭包:注意闭包引用的大对象。
  • 事件监听器:使用 emitter.once 或手动移除。
  • 定时器未清除:清除 setIntervalsetTimeout

3.3 调整垃圾回收参数

可以通过 Node.js 命令行参数调整 GC 行为,例如:

node --max-old-space-size=4096 app.js  # 限制老生代内存为 4GB

4. 数据库优化

数据库往往是瓶颈所在。

4.1 建立索引

确保查询字段有索引。MongoDB:

db.users.createIndex({ email: 1 });

4.2 使用连接池

数据库客户端默认有连接池,但需根据负载调整大小。例如 MySQL:

const pool = mysql.createPool({
  connectionLimit: 10,
  // ...
});

4.3 优化查询

只返回需要的字段,使用分页,避免 N+1 查询。使用 ORM 时注意 eager loading。

4.4 读写分离

对于读多写少的场景,可以使用主从复制,将读请求分发到从库。

5. 缓存策略

缓存可以大幅减少数据库和后端计算的压力。

5.1 内存缓存

使用 Node.js 的内存缓存(如 node-cache)存储频繁访问的数据。注意内存限制。

const NodeCache = require('node-cache');
const myCache = new NodeCache({ stdTTL: 600 }); // 缓存10分钟

function getUser(id) {
  let user = myCache.get(id);
  if (user) return Promise.resolve(user);
  return db.getUser(id).then(user => {
    myCache.set(id, user);
    return user;
  });
}

5.2 分布式缓存(Redis)

Redis 是更强大的缓存解决方案,支持持久化和数据结构操作。

const redis = require('redis');
const client = redis.createClient();

async function getCachedData(key) {
  const data = await client.get(key);
  if (data) return JSON.parse(data);
  const fresh = await fetchData();
  await client.setEx(key, 3600, JSON.stringify(fresh));
  return fresh;
}

5.3 HTTP 缓存

设置正确的 HTTP 头(Cache-ControlETag),让浏览器或 CDN 缓存响应。

res.setHeader('Cache-Control', 'public, max-age=3600');

6. 网络层优化

6.1 启用 Gzip 压缩

使用 compression 中间件压缩响应体:

const compression = require('compression');
app.use(compression());

6.2 使用 HTTP/2

Node.js 支持 HTTP/2,可以减少连接开销。需配合 HTTPS 使用。

6.3 静态文件服务

将静态文件交给 Nginx 或 CDN 处理,而不是 Node.js。

7. 监控与性能分析

没有测量就没有优化。使用工具监控应用性能。

  • Node.js 内置process.cpuUsage()process.memoryUsage()
  • PM2 监控pm2 monit 查看 CPU/内存。
  • APM 工具:New Relic、Datadog、Elastic APM 等提供深入分析。
  • 日志分析:通过日志统计响应时间,使用 morgan 记录请求耗时。
const morgan = require('morgan');
morgan.token('response-time-ms', (req, res) => res.getHeader('X-Response-Time-ms'));
app.use(morgan(':method :url :status :response-time-ms ms'));

8. 其他优化技巧

  • 使用工作线程处理 CPU 密集型任务:避免阻塞主线程。
  • 升级 Node.js 版本:新版本通常包含性能改进和 bug 修复。
  • 减少不必要的依赖:精简 node_modules,使用 npm prune
  • 使用 npm ci 而非 npm install 在 CI 中,速度更快且严格。
  • 设置 NODE_ENV=production:许多库(如 Express)会启用生产优化。

9. 性能优化清单

  • [ ] 代码使用异步 I/O,避免同步阻塞。
  • [ ] 应用运行在集群模式(PM2 或 cluster)。
  • [ ] 使用缓存(内存或 Redis)减少数据库压力。
  • [ ] 数据库查询已优化并建立索引。
  • [ ] 开启 Gzip 压缩。
  • [ ] 设置适当的 max-old-space-size
  • [ ] 定期检查内存泄漏(堆快照)。
  • [ ] 使用 APM 监控生产环境性能。
  • [ ] NODE_ENV 设为 production。
总结: 性能优化是一个持续的过程,需要结合监控数据和业务特点进行。从代码规范开始,到架构设计,再到运维监控,每个环节都有优化空间。希望本章的策略能帮助你打造高性能的 Node.js 应用。