Node.js 性能监控与优化

Node.js 应用的性能指标、监控方案和优化策略

问题

如何对 Node.js 应用进行性能监控和优化?

解答

性能指标

Node.js 性能主要关注以下四个方面:

CPU

  • CPU 负载:某个时间段内,占用以及等待 CPU 的进程总数
  • CPU 使用率:CPU 时间占用状况,等于 1 - 空闲 CPU 时间 / CPU 总时间

Node 应用通常不会消耗很多 CPU,如果 CPU 占用率高,说明存在大量同步操作阻塞了异步任务回调。

内存

通过 process.memoryUsage() 获取内存使用情况:

const os = require('os');

// 获取当前 Node 内存堆栈情况
const { rss, heapUsed, heapTotal } = process.memoryUsage();
// 获取系统空闲内存
const sysFree = os.freemem();
// 获取系统总内存
const sysTotal = os.totalmem();
  • rss:Node 进程占用的内存总量
  • heapTotal:堆内存的总量
  • heapUsed:实际堆内存的使用量
  • external:外部程序的内存使用量,包含 Node 核心 C++ 程序的内存使用量

Node 进程的最大内存容量为 1.5GB,需要注意避免内存泄露。

磁盘 I/O

硬盘 I/O 开销很大,花费的 CPU 时钟周期是内存的 164000 倍。可以使用 Redis、Memcached 等内存缓存来优化高频访问、生成代价高的数据。

网络

网络性能影响服务响应速度和吞吐量。

性能监控

使用 Easy-Monitor 2.0 进行性能监控,这是一个轻量级的 Node.js 内核性能监控工具。

在项目入口文件中引入:

const easyMonitor = require('easy-monitor');
easyMonitor('你的项目名称');

启动项目后,访问 http://localhost:12333 即可查看监控界面。

性能优化

使用最新版本 Node.js

新版本带来 V8 引擎更新和 Node.js 内部代码优化,性能提升明显。

正确使用 Stream

对于大文件,使用流式传输而不是一次性读入内存:

const http = require('http');
const fs = require('fs');

// 不推荐:一次性读入内存
http.createServer(function (req, res) {
    fs.readFile(__dirname + '/data.txt', function (err, data) {
        res.end(data);
    });
});

// 推荐:使用流
http.createServer(function (req, res) {
    fs.createReadStream(__dirname + '/data.txt').pipe(res);
});

代码层面优化

合并数据库查询,减少查询次数:

// 不推荐:多次查询
for (let user_id of userIds) {
    let account = await user_account.findOne(user_id);
}

// 推荐:批量查询
const user_account_map = {};
const accounts = await user_account.find({ user_id: { $in: userIds } });
accounts.forEach(account => {
    user_account_map[account.user_id] = account;
});
for (let user_id of userIds) {
    let account = user_account_map[user_id];
}

内存管理优化

V8 将内存分为新生代和老生代。避免内存泄露,减少不必要的内存占用:

// 不推荐:造成内存泄露
const leak = [];
const buffer = fs.readFileSync(__dirname + '/source/index.htm');

app.use(mount('/', async (ctx) => {
    ctx.status = 200;
    ctx.type = 'html';
    ctx.body = buffer;
    leak.push(fs.readFileSync(__dirname + '/source/index.htm')); // 内存泄露
}));

// 推荐:复用 buffer
const buffer = fs.readFileSync(__dirname + '/source/index.htm');

app.use(mount('/', async (ctx) => {
    ctx.status = 200;
    ctx.type = 'html';
    ctx.body = buffer;
}));

使用对象池机制,复用频繁创建和销毁的对象,减少内存抖动,提升性能。

关键点

  • 监控 CPU、内存、I/O、网络四个核心指标,Node 进程最大内存为 1.5GB
  • 使用 Easy-Monitor 等工具进行实时性能监控
  • 大文件传输使用 Stream 而不是一次性读入内存
  • 合并数据库查询,使用内存缓存(Redis/Memcached)优化高频访问
  • 避免内存泄露,使用对象池复用频繁创建的对象