2811 字
约 9 分钟
16
S16-X12 · 指标体系与告警:RED 与 USE
2024-12-31
2026-11-08

凌晨被告警叫醒,打开看板:CPU 70%、QPS 正常、GC 时间不长,八十个图没有一个能回答"用户现在到底受不受影响"。指标体系的第一个问题从来不是"能不能采到",而是"这个图给谁看、看完要做什么决定"。

RED 与 USE 是两把不同的尺子:一把量服务,一把量资源。混着用会得出"CPU 高所以要扩容"这类结论,而真正的原因可能是下游慢导致的线程堆积。


1. 四种指标类型

类型 语义 聚合特性 典型用途
Counter 单调递增的累计值 rate 后可跨实例求和 请求数、错误数、字节数
Gauge 瞬时值 可求和,求平均常无意义 队列深度、连接数、线程数
Histogram 分桶计数 + 总和 + 计数 桶可跨实例合并,再算分位 延迟、请求/响应大小
Summary 客户端算好的分位 分位不可跨实例合并 单进程内部分位(慎用)

选型的唯一判据是你要不要跨实例聚合。要,就必须用 Histogram;Summary 一旦跨实例相加就是错的,而错得很安静。


2. Histogram 分桶怎么选

management:
  metrics:
    distribution:
      percentiles-histogram:
        http.server.requests: true
      slo:
        http.server.requests: 50ms,100ms,200ms,500ms,1s,2s
原则 说明
覆盖 SLO 阈值附近 阈值 300ms,桶里必须有 250/300/400 这种边界
按倍数而非等距 10/20/40/80/160 比 10/20/30/40 更有用
桶不宜过多 每个桶一组时间序列,直接乘进基数
改边界要留痕 边界一变,历史曲线不可比,需在变更记录里注明
用 slo 而非手写桶 Micrometer 的 SLO 边界会同时产出 le 标签,便于 histogram_quantile

按 Micrometer 1.13.x 的行为核对:percentiles-histogram 打开后才导出 _bucket 序列,没打开时 histogram_quantile 直接无数据——这是"分位数查不出来"的第一号原因。


3. RED:服务视角

信号 定义 指标形态 告警用法
Rate 每秒请求数 rate(..._count[5m]) 突降(流量断了)比突增更值得报
Errors 失败占比 5xx 或业务失败 / 总数 与 SLO 预算挂钩
Duration 延迟分布 _bucket 算分位 分位超阈值且持续
# 错误率
sum(rate(http_server_requests_seconds_count{job="order", status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_count{job="order"}[5m]))
# p99:先按 le 合并桶,再算分位
histogram_quantile(0.99,
  sum by (le) (rate(http_server_requests_seconds_bucket{job="order"}[5m])))

RED 的三个信号要一起看:只有错误率没有流量,分不清"错误变多"还是"流量掉了";只有延迟没有流量,分不清"变慢"还是"没人来"。


4. USE:资源视角

信号 定义 例子 判读
Utilization 资源忙碌时间占比 CPU、磁盘 IO、带宽 看拐点,不看绝对值
Saturation 排队程度 run queue、连接池等待、队列深度 持续大于 0 就该关注
Errors 资源层错误 网卡丢包、磁盘错误、OOM kill 出现即查
# 磁盘:利用率与加权 IO 时间(后者近似排队量)
rate(node_disk_io_time_seconds_total[5m])
rate(node_disk_io_time_weighted_seconds_total[5m])

USE 的价值在于它比 RED 更早:饱和度上升时用户还没感知,等到错误率上来已经晚了。但 USE 单独使用会误导——CPU 70% 对一个 IO 密集型服务完全正常,对另一个服务可能已经是瓶颈前兆。


5. 两套怎么组合

层次 方法论 回答的问题
服务 RED 用户是否受影响
实例 RED(按实例分组)+ USE 是哪一台
依赖(DB/缓存/MQ) RED + USE 是不是下游拖的
主机 / 容器 USE 资源是否成为瓶颈

下钻顺序固定:服务 RED 异常 → 按实例切 RED → 该实例 USE → 横向到依赖。反过来(先看 CPU)会走向"CPU 高所以是它",而高 CPU 常常是结果而不是原因——例如下游变慢导致线程堆积、重试风暴。


6. 延迟指标的四个陷阱

陷阱 表现 正确做法
用平均值 平均 50ms,p99 2s,用户仍投诉 至少看 p95/p99
跨实例平均分位 各实例 p99 求平均 ≠ 全局 p99 先合并桶再算分位
只统计成功请求 超时被排除,延迟反而变好看 分母包含失败与超时
分位相加 p99 + p99 无意义 用 Histogram 合并
# 正确:合并桶后算分位
histogram_quantile(0.99, sum by (le) (rate(http_server_requests_seconds_bucket[5m])))
# 错误:对已经是分位的序列再平均
avg(http_server_requests_seconds{quantile="0.99"})

第二条在实际事故里最常见:二十个实例各自 p99 正常,全局 p99 超标——因为少数实例贡献了长尾,一平均就被稀释掉了。


7. 告警设计

原则 反例 做法
症状优先于原因 CPU > 80% 就 page 错误率、延迟超 SLO 才 page
多条件组合 单点毛刺即告警 rate + 持续时间同时满足
for 抑制抖动 瞬时抖动刷屏 for: 5m
分级与升级 全部 P1 P1 用户可见,P2 容量风险,P3 趋势
带 runbook 告警只说"异常" 附首个排查命令与链接
groups:
  - name: red
    rules:
      - alert: OrderHighErrorRate
        expr: |
          sum(rate(http_server_requests_seconds_count{job="order", status=~"5.."}[5m]))
          / sum(rate(http_server_requests_seconds_count{job="order"}[5m])) > 0.02
        for: 5m
        labels: {severity: page}
        annotations:
          summary: "下单错误率 > 2%(5 分钟)"
          runbook_url: "https://wiki/runbook/order-5xx"

原因告警不是不能留,而是不该直接 page。CPU 高、GC 频繁这类信号放进"诊断面板"或 P3 通知,让它们在排查时被看见,而不是在凌晨叫醒人。


8. SLO 与错误预算

概念 定义 例子(30 天)
SLI 可测量的服务质量指标 成功请求比例、p99 延迟
SLO SLI 的目标值 99.9%
错误预算 1 - SLO 换算成额度 约 43 分钟不可用,按 99.9% 核对
燃尽率 预算消耗速度 1 小时烧掉 5% → 高燃尽,提前告警
# 燃尽倍数:错误率 / 预算率(SLO 99.9% → 预算率 0.001)
(1 - (sum(rate(http_server_requests_seconds_count{status!~"5.."}[1h]))
      / sum(rate(http_server_requests_seconds_count[1h])))) / 0.001

预算烧完之后要有明确动作:冻结非必要发布、把工程时间切到可靠性、复盘触发点。没有动作的预算制度会退化成一张报表——数字每天都在,没人因此改变行为。


9. 黄金信号及其局限

信号 覆盖 局限
延迟 / 流量 / 错误 / 饱和度 服务健康状况 不覆盖业务正确性
— — "下单成功但金额算错"不会触发任何一条

补法是加业务指标:下单成功率、支付成功率、库存扣减一致率、退款率。它们和 RED 是两层——RED 说"系统是否在正常工作",业务指标说"工作得对不对"。


10. 基数爆炸:标签是最贵的资源

标签 基数风险 处理
userId / orderId / traceId 无上限 绝不作为指标标签
含 id 的 url 高 用模板化路由 /orders/{id}
错误消息原文 极高 归一化成 error_code
service / instance / status / method / route 可控 可以作为标签
// 反例:用户维度进指标标签,序列数随用户数线性增长
Counter.builder("order.created").tag("userId", uid).register(registry).increment();

// 正例:用户维度留给日志与 span,指标只保留低基数维度
Counter.builder("order.created").tag("channel", channel).register(registry).increment();

基数一旦爆掉,症状不是"报错",而是采集变慢、查询超时、存储暴涨,而且很难回溯定位到是哪次提交引入的。上线前用 count by (__name__) 之类的方式抽查序列数,成本远低于事后清理。


11. 看板的四层组织

层 内容 回答
概览 SLO、错误预算、核心 RED 现在有没有事
服务 每个服务的 RED + 依赖调用 是哪个服务
实例 实例级 RED + USE 是哪一台
依赖 DB / 缓存 / MQ 的 RED + USE 是不是下游

四层的意义是每层只回答一个问题。把八十个图堆在一页,等于没有分层:看的人要自己找哪条曲线异常,而这正是告警该干的活。


12. 排错:六种指标体系病灶

现象 原因 处理
histogram_quantile 无数据 没开 percentiles-histogram 打开并确认 _bucket 序列存在
分位数突然失真 桶边界被改过 修改变更记录,或换新指标名重来
存储暴涨 高基数标签 找出高基数标签并降维
告警天天响没人管 原因告警 page、无 runbook 改症状告警 + 补处置步骤
扩容后延迟没降 瓶颈在依赖而非本机 按 RED → 实例 → 依赖的顺序重查
平均值正常但用户投诉 长尾被平均 改看 p95/p99,并核对失败请求是否被排除

第一条和最后一条都指向同一件事:指标看错了比没有指标更危险,因为它会给出确定的错误结论。


13. 边界:指标回答不了的问题

场景 指标的局限 该补什么
单次请求为什么慢 只有聚合视角 链路追踪(见 S16-X11)
代码里哪一行热 只有方法级或接口级 profiling
数据是否正确 指标衡量不了语义 业务校验、对账
偶发一次抖动 被聚合抹平 日志与事件
根因链 只知道"哪一段异常" 追踪 + 变更记录

一句话:指标负责发现"有异常",追踪负责解释"异常在哪",日志负责说明"为什么"。 三者缺一条,排障就会退化成猜。


14. 自测

  1. 为什么 Summary 的分位不能跨实例合并,而 Histogram 可以?
  2. slo 边界与 percentiles-histogram 各自起作用在哪一步?
  3. RED 里如果只有错误率没有流量,会误判成什么?
  4. 二十个实例 p99 都正常、全局 p99 超标,最可能是什么原因?
  5. 为什么原因类告警(CPU、GC)不该直接 page?
  6. 错误预算烧完之后,团队应该做的三件事是什么?
S16-X12 · 指标体系与告警:RED 与 USE
https://www.clxhxhhr.top/posts/5835/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。