文档目录

1.1 指标分四层,缺一层就无法定位

上一节:无 | 下一节:1.2 百分位不可平均 配套代码:01-metrics-and-slo/01-four-layers-of-metrics


一句话结论

性能指标分四层:业务层、延迟层、资源层、饱和度层。 只监控前两层,你会在「服务变慢了」和「为什么慢」之间卡住;而饱和度层是最早的预警信号,它在用户受影响之前就开始报警。


一、用汽车仪表盘理解「为什么指标要分层」

想象你开车,车上只有一个速度表:

  • 速度表显示 60 km/h —— 车在正常行驶。
  • 突然,速度掉到 20 km/h。

你知道出问题了,但完全不知道原因:是没油了?发动机过热?轮胎漏气?还是前面堵车?

现在把仪表盘补全:

仪表 对应层级 告诉你什么
速度表 业务层 车跑得快不快(QPS)
乘客的等待时间 延迟层 体验好不好(P99)
油量、转速、水温 资源层 消耗了什么(CPU / 内存 / GC)
水温警告灯、油量警示灯 饱和度层 离出问题还有多远

关键洞察:速度和等待时间都是「已经发生的结果」,而警告灯是「即将发生问题的预警」。

  • 速度掉到 20 km/h 时,问题已经发生了,你只能被动应对。
  • 水温警告灯亮起时,你还有时间靠边停车。

性能工程里最值钱的指标,是那些「用户还没受影响,但系统已经在告警」的指标。 这就是饱和度层。


二、四层指标详解

第 1 层:业务层 —— 「服务好不好用」

指标 含义 常见坑
QPS / RPS 每秒处理的请求数 分母应该是「成功完成」的请求
错误率 失败请求占比 必须与延迟一起看(否则会「优化」出更多错误)
超时率 超时请求占比 与错误率分开统计(超时和报错的处理方式不同)
Apdex 满意度指标(快 / 可接受 / 慢三档) 阈值必须来自业务可感知边界,不能拍脑袋

回答的问题:用户现在体验如何?

缺陷:只知道「好不好」,不知道「为什么」。

第 2 层:延迟层 —— 「用户等了多久」

指标 含义
P50 / P90 / P95 / P99 / P999 各百分位的响应时间
最大值 极端情况(不适合做告警,因为波动太大)
客户端延迟 vs 服务端延迟 两个不同的数字(见 1.3)

回答的问题:用户的等待时间分布是什么样的?

缺陷:知道「慢」,不知道「慢在哪」。

第 3 层:资源层 —— 「消耗了什么」

类别 关键指标 说明
CPU user / sys / iowait / steal sys 高通常意味着频繁系统调用、锁竞争或页错误;steal 高说明被虚拟机邻居抢了 CPU
内存 堆使用、分配速率(MB/s) 分配速率比堆占用更能预测 GC 压力
GC 次数、停顿时间、各代回收频率 停顿直接体现在延迟尾部
磁盘 IOPS、吞吐、await 「利用率 100%」不等于慢,要看 await
网络 带宽、连接数、重传 —

回答的问题:为了达到现在的性能,消耗了什么?

缺陷:CPU 高不代表用户受影响(可能是在做有用的工作);CPU 低也不代表没事(可能在等 IO)。

第 4 层:饱和度层 —— 「离上限还有多远」⭐

资源 饱和度指标 危险信号
数据库连接池 active / idle / pending pending > 0 持续存在 = 已经在排队
线程池 / 协程调度器 队列深度、活跃线程数 队列持续非空
队列(消息、任务) 队列长度、消费延迟 持续增长 = 消费速度跟不上
缓存 命中率、容量占用 命中率下降、频繁驱逐
容器 CPU 节流计数(nr_throttled) 持续增长 = 被配额周期性掐停
磁盘 IO 队列深度 队列长 = await 即将飙升

回答的问题:我离出问题还有多远?

这一层为什么最重要:

  1. 它比延迟更早报警。 连接池 pending 开始上升时,延迟可能只涨了 5%;等延迟明显恶化时,你已经没有调整时间了。
  2. 它直接指向瓶颈。 「pending > 0」直接告诉你「问题在连接池」,不需要再猜。
  3. 它不受协调遗漏影响。 延迟数据可能被压测方式污染(第 0 章),但「队列里积压了多少」是一个客观事实。

三、缺层的后果:一个排查失败的例子

背景:一个订单查询接口,P99 从 80 ms 涨到 400 ms。

只有业务层 + 延迟层(大多数团队的现状)

观察到的数据:
  QPS:稳定在 600(没变)
  P99:80ms → 400ms(恶化 5 倍)
  CPU:45%(不高)

结论:CPU 不高、QPS 没变,但延迟涨了。不知道问题在哪。 接下来只能靠猜:是不是数据库慢了?是不是有慢查询?是不是网络问题?——于是开始了漫长的试探。

四层齐全

业务层:QPS 600(稳定),错误率 0.1%(稳定)
延迟层:P99 80ms → 400ms
资源层:CPU 45%,GC 停顿正常,磁盘 await 正常
饱和度层:数据库连接池 pending 从 0 → 18 ⭐

结论立刻清晰:

  • CPU 不高 + 连接池排队 → 不是「算不过来」,而是「等数据库的时间变长了」。
  • 下一步该查的是:数据库侧的执行计划、锁等待、或者连接池大小。

这一步省下的是几个小时。 而且注意:这个结论不是从延迟数据推出来的,而是从饱和度数据推出来的。


四、一个容易忽略的点:指标要「可分解」

四层指标齐全还不够,还要能沿调用链分解。否则你会知道「数据库类请求整体变慢了」,但不知道是哪一条 SQL。

建议的最小分解粒度:

接口层:http_server_requests{uri="/orders/{id}"}
   ├── 依赖层:dep.postgres{operation="findById"}
   ├── 依赖层:dep.redis{operation="get"}
   └── 依赖层:dep.downstream{service="user-service"}

有了这个结构,才能算出第 1.3 节 要讲的「自身开销 = 本层延迟 − 各依赖延迟之和」。

一个经验法则:如果你无法回答「这个接口的 P99 里,数据库占了多少毫秒」,说明你的埋点粒度不够。


五、本节小结

  1. 指标分四层:业务 / 延迟 / 资源 / 饱和度,缺一层就会卡在排查半路。
  2. 饱和度层是最值钱的一层:它是领先指标,在用户受影响之前就报警,而且不受压测方式污染。
  3. 只有业务层和延迟层时,你只能知道「慢」,无法知道「为什么慢」。
  4. 指标还要能沿调用链分解(至少拆到依赖级),否则定位不到具体环节。
  5. 「资源层正常 + 饱和度层排队」这个组合,直接指向「不是算不过来,而是在等」——这是最常用的一条判读。

六、自测

  1. 某服务的 CPU 是 30%,P99 是 500 ms(SLO 要求 200 ms)。请说出你接下来要看的三个具体指标,以及每个指标能排除/确认什么假设。
  2. 为什么说「饱和度指标不受协调遗漏影响」?延迟指标受什么影响?
  3. 一个团队只监控 QPS、错误率、P99、CPU 这四项。请指出他们缺了哪一层,以及在一次「数据库变慢」的故障中,这个缺失会带来什么具体后果。
  1. ① 数据库连接池的 pending / active:如果 pending > 0,说明在排队,瓶颈可能是连接数不足或查询变慢;如果 pending 一直是 0,则排除连接池。② GC 停顿时间(P99)与分配速率:若 GC 停顿的尖刺与 P99 尖刺在时间上对齐,则 GC 是嫌疑;若不对齐,可排除。③ 依赖级延迟(dep.postgres 的 P99):如果它占了总延迟的绝大部分,问题在数据库;如果它很小而总延迟很大,问题在应用自身(序列化、锁、CPU 密集计算)。还可以看 wall-clock 火焰图确认「时间花在等待还是计算」。
  2. 饱和度指标记录的是「此刻队列里积压了多少」「池子里有多少在等」这类客观状态量,它由服务端的真实状态决定,与压测工具怎么发请求无关。而延迟指标是「观测到的样本统计量」——闭环压测会在服务端变慢时减少发压,导致慢样本没被采集到,于是 P99 被低估。饱和度不受这个影响,因为积压是真实发生的。
  3. 缺了饱和度层(具体说:连接池 pending、队列深度)。后果:「数据库变慢」时,连接池很快会被占满并开始排队,但如果没有 pending 指标,你只能看到「延迟上升、CPU 不高」——这个组合无法区分「数据库慢」「连接池太小」「下游阻塞」三种情况。你会去查数据库日志、看慢查询、翻应用代码,而实际上最直接的答案(连接池排了 18 个)就在你手边。更糟的是,你可能会错误地加大连接池,把压力转移到数据库,让问题恶化。