如果拿到一个nodejs服务系统的指标,如何判断这个这个服务有问题?
可以通过三个指标(Utilization、Saturation、Errors)来监控和评价 Node.js 服务健康状态的核心维度,通常被称为 USE 方法论(由 Brendan Gregg 提出),是排查性能问题和评估服务稳定性的黄金准则。
- Utilization(利用率):资源在时间段内被使用的百分比
- Saturation(饱和度):资源排队等待的数量
- Errors(错误数):运行过程中出现的错误数量
Utilization 利用率
指某类资源在一段时间内被实际使用的时间占总时间的百分比,核心是衡量资源有没有被充分利用,但利用率过高反而会导致性能问题。
Node.js 是单线程(主线程)+ 异步 I/O 的架构,所以利用率监控重点集中在以下资源:
-
CPU 利用率
-
Node.js 主线程(V8 执行 JS 代码)是单线程的,所以单核心 CPU 利用率是关键(而非整机 CPU)。
-
正常范围:建议控制在 70%-80% 以内。
- 利用率 < 50%:资源闲置,服务有扩容空间;
- 利用率 80%-90%:接近饱和,需警惕;
- 利用率 > 90%:主线程被阻塞(比如同步计算、复杂正则、循环耗时),异步 I/O 也会排队,响应延迟飙升。
-
示例:如果 10 秒内 Node.js 主线程有 8 秒在执行代码,CPU 利用率就是 80%。
-
-
内存利用率
- 指 Node.js 进程占用的内存(堆内存 / 非堆内存)占配置的内存上限(如
--max-old-space-size)的百分比。 - 示例:如果配置 Node.js 最大堆内存为 1GB,当前使用了 700MB,内存利用率就是 70%。
- 注意:内存利用率高不一定是问题,但持续接近 100% 会触发频繁的 GC(垃圾回收),甚至导致 OOM(内存溢出)。
- 指 Node.js 进程占用的内存(堆内存 / 非堆内存)占配置的内存上限(如
-
I/O 利用率
- 包括磁盘 I/O(读写文件)、网络 I/O(HTTP 请求、数据库连接)的利用率。
- 示例:Node.js 服务的网络端口每秒能处理 1000 个请求,当前每秒处理 800 个,网络 I/O 利用率就是 80%。
Saturation 饱和度
指资源因被占满而导致请求 / 任务排队等待的数量或时长,核心是衡量 “资源不够用了,任务开始积压”,是比利用率更提前的性能预警指标。
Node.js 中常见的监控对象有:
-
事件循环延迟(Event Loop Delay)
- Node.js 所有 JS 代码都在事件循环中执行,若主线程阻塞,新的回调(如 HTTP 请求处理、定时器)会排队,排队的时长就是事件循环延迟。
- 正常范围:延迟 <100ms;若> 500ms,用户会感知到接口响应慢。
- 示例:事件循环本应每 10ms 处理一次回调,结果因 CPU 利用率 95%,变成每 200ms 处理一次,排队的回调数量就是饱和度。
// 示例:监控事件循环延迟(使用 node-event-loop-lag 库)
// 先安装:npm install node-event-loop-lag
const eventLoopLag = require('node-event-loop-lag');
// 每 500ms 检测一次事件循环延迟(饱和度核心指标)
setInterval(() => {
const lag = eventLoopLag(); // 单位:毫秒
console.log(`事件循环延迟: ${lag.toFixed(2)}ms`);
if (lag > 100) {
console.warn('事件循环饱和度高,任务开始排队!');
}
}, 500);
-
连接队列饱和度
- Node.js 服务的 HTTP 服务器有一个 “待处理连接队列”(由
backlog参数控制),当并发请求超过服务处理能力时,新请求会进入队列等待。 - 示例:队列长度配置为 100,当前排队的请求有 80 个,饱和度就是 80%;若队列满了,新请求会被直接拒绝(报错
ECONNREFUSED)。
- Node.js 服务的 HTTP 服务器有一个 “待处理连接队列”(由
-
数据库连接池饱和度
- 若 Node.js 服务使用数据库连接池(如 MySQL、Redis),当连接池中的所有连接都被占用,新的数据库请求会排队,排队的请求数就是饱和度。
- 示例:连接池配置 20 个连接,当前 20 个都在使用,且有 15 个请求排队等待连接,饱和度就是 15(排队数)。
Errors 错误数
指资源在使用过程中出现的错误数量或错误率,核心是衡量 “服务运行中出现的异常”,错误会直接导致请求失败,是服务可用性的核心指标。
Node.js 中常见的监控对象:
-
业务错误
- 服务处理请求时的业务逻辑错误(如参数校验失败、数据不存在),通常表现为 HTTP 4xx 状态码(400、404、403)。
- 示例:每秒 1000 个请求中,有 50 个返回 400 错误,错误率就是 5%。
-
系统错误
- 服务运行时的底层错误(如内存溢出、文件读写失败、数据库连接断开),通常表现为 HTTP 5xx 状态码(500、502、503)或进程崩溃。
- 示例:Node.js 进程因 OOM 崩溃(错误
JavaScript heap out of memory)、数据库连接超时(错误ETIMEDOUT),这些都要统计错误数。
-
资源错误
- 资源本身的错误(如文件不存在、端口被占用),会直接导致服务无法提供功能。
- 示例:Node.js 尝试监听 80 端口但被占用,抛出
EADDRINUSE错误,错误数 +1。
总结一下:
- 利用率(Utilization) :衡量资源 “忙不忙”,重点监控 Node.js 主线程 CPU、内存、I/O 的使用占比,过高会导致性能下降;
- 饱和度(Saturation) :衡量资源 “够不够用”,核心看事件循环延迟、请求排队数,是提前预警性能问题的关键;
- 错误数(Errors) :衡量服务 “有没有故障”,关注 5xx/4xx 错误、系统异常,直接反映服务可用性。
这三个维度结合,能全面评估 Node.js 服务的健康状态,是从 “资源使用” 到 “任务积压” 再到 “运行异常” 的完整监控闭环。
CPU 监控
计算密集型
计算和逻辑判断量非常大且集中的任务类型。
特点:
- 主要占用CPU资源,又称CPU密集型;
- 当任务数等于CPU核心数时,CPU运行效率最高,也就是CPU跑满了;
关键认知:计算密集型任务会显著占用CPU资源。
IO密集型
磁盘读取和输出数据量非常大的任务类型。
特点:
- IO操作时间远大于CPU和内存运行时间;
- 任务大部分时间在等待IO操作完成;
- CPU消耗相对较小但并非完全空闲;
系统瓶颈:主要出现在硬盘性能上。
常见误解:90%利用率可能被误认为CPU 90%时间都在执行计算。
实际情况:CPU可能处于多种状态
- Busy:真正执行计算任务
- Waiting(stalled):等待内存访问(主要是读内存)
- Waiting(idle):完全空闲
停顿状态:CPU在等待内存返回时不能执行指令,应用代码也会暂停 性能瓶颈:由于CPU发展远快于内存,大部分时间CPU在等待内存返回 重要结论:高CPU利用率不一定由计算密集型任务导致,过多的内存操作同样可能造成。
排查计算密集型CPU的利器:火焰图。
火焰图(Flame Graph)是专精于运维领域的性能分析工具。
坐标含义:
- X轴:表示CPU运行时间长度
- Y轴:从上到下表示从应用层到底层系统
分析要点:
- 条幅宽度表示占用CPU时间的多少
- 宽条幅表示该逻辑占用大量CPU时间
- 可逐层分析具体消耗CPU时间的代码逻辑
下面演示下如何用 0x 这个npm包来测试CPU。
新建app.js
"use strict";
import express from "express";
const server = express();
function a() {
for (let i = 0; i < 1e8; i++) {}
}
function b() {
for (let i = 0; i < 2e8; i++) {}
}
server.get("/", (req, res, next) => {
a();
b();
res.send("hello world");
});
server.listen(3000, () => {
console.log("listen at: http://127.0.0.1:3000/");
});
process.on("SIGINT", function () {
console.error("Caught SIGINT, shutting down.");
server.close();
});
安装依赖:
pnpm add 0x autocannon -D
脚本命令:
"scripts": {
"test": "NODE_ENV=production 0x -P 'autocannon http://localhost:3000/' ./app.js"
},
用 0x 工具来启动 app.js,然后用压测工具 utocannon 来进行压测,默认并发 10 连接,持续 10 秒。
执行后会生成一个文件:
用浏览器打开html文件,就可以看到 a函数 和 b函数分别利用CPU的时长了,刚好b函数差不多是a函数的两倍。
通过 ox 火焰图就能分析一段代码运行CPU的时长了。