文档目录

8.4 生产观测:SLI 与错误预算

上一节:8.3 全链路性能门禁 | 下一节:8.5 告警设计:燃烧率与领先指标 配套代码:08-continuous-performance/04-production-observability


一句话结论

错误预算(Error Budget)= 性能的「流量套餐」。 如果 SLO 是 99.9%,那么每月有 0.1% 的请求「允许违约」——这个额度用完了,就该停止发布新功能,先修稳定性。


一、用「手机流量套餐」理解错误预算

你每月有 10 GB 流量:

情况 你的行为
用了 3 GB 随便用,不用担心
用了 7 GB 开始留意,但还能用
用了 9.5 GB 关掉视频,只用必要功能
用完了 限速,或者买加量包

错误预算完全一样:

SLO = 99.9%(30 天窗口)
→ 错误预算 = 0.1% 的请求可以违约
→ 按 100 万请求/天算,30 天共 3000 万请求
→ 预算 = 3 万个请求可以失败

用了 1 万(33%)→ 健康,正常发布
用了 2.4 万(80%)→ 警告,谨慎发布
用了 3 万(100%)→ 冻结发布,先修稳定性

关键洞察:错误预算把「性能」变成了一个可以量化管理的资源——而不是一个模糊的「要快、要稳」。


二、SLI 采集的三个要求

要求一:低开销(可以长期运行)

手段 开销 适用
Prometheus 指标(直方图) 极低 ✅ 首选
JFR 滚动录制 约 1%–2% ✅ 可以常开
分布式追踪(1% 采样) 低 ✅
分布式追踪(100% 采样) 中–高 ⚠️ 只在关键路径
高频采样 profiler 5%–15% ❌ 只在排障时

原则:观测本身不能成为性能问题(第 0 章 0.9 节)。

要求二:覆盖完整(能回答「用户经历了什么」)

必须采集的 SLI:

类型 指标 说明
可用性 成功率 / 错误率 分母用总请求数(不是成功数)
延迟 P50/P95/P99/P999(直方图) 必须是直方图,不能是客户端算好的分位数
吞吐 QPS —
饱和度 连接池 pending、队列深度、GC 停顿、容器节流 领先指标 ⭐
业务 订单成功率、支付成功率 最终反映在业务指标上

要求三:测量点明确(且与 SLO 对齐)

客户端(RUM/压测)  → 最接近用户体验,但需要前端配合
网关/负载均衡       → 生产长期监控的首选(第 1 章 1.3 节)
应用内埋点          → 最细,用于定位

生产 SLO 通常用网关或客户端的视角——因为那才是用户经历的。


三、SLO 的定义(持续监控版)

第 1 章 1.4 节的 SLO 是「验收用」的(含负载前提、测量点、时长)。生产 SLO 是「监控用」的,要简化到能自动计算:

# SLO 定义(示例)
slos:
  - name: "订单查询可用性"
    sli: "http_requests_total{uri='/orders/{id}', status!~'5..'} / http_requests_total{uri='/orders/{id}'}"
    target: 0.999          # 99.9%
    window: "30d"
    error_budget: "0.1%"

  - name: "订单查询延迟"
    sli: "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{uri='/orders/{id}'}[5m]))"
    target: "< 0.2"        # 200ms
    window: "30d"
    condition: "在 QPS > 100 时"    # ⚠️ 负载前提不能丢

  - name: "下单成功率"
    sli: "business_orders_success_total / business_orders_total"
    target: 0.995
    window: "30d"

注意第三项:业务 SLI 比技术 SLI 更接近真实价值——技术指标全绿但下单成功率下降,说明有更深的问题。

三个设计要点

要点 说明
窗口要够长 30 天是常见选择(太短易受单次事件影响,太长反馈太慢)
负载前提不能丢 低流量时 P99 容易"达标"(样本少);要加 QPS > X 条件
不要设太多 SLO 每个服务 3–5 条核心 SLO 就够;设太多会分散注意力

四、错误预算怎么用

用于决策:该不该继续发布

预算消耗 < 50%    → 正常发布
预算消耗 50%–80%  → 谨慎发布(加强金丝雀观察)
预算消耗 80%–100% → 只发布修复性变更
预算耗尽          → ⛔ 冻结功能发布,全部精力投入稳定性

用于告警:燃烧率(下一节详述)

错误预算的消耗速度比绝对消耗量更重要:

预算耗尽 50%,但用了 29 天 → 消耗速度正常,不用报警
预算耗尽 50%,但只用了 2 小时 → 燃烧率极高,立刻告警 ⚠️

用于沟通:给业务方的语言

技术说法 业务说法
「P99 从 200ms 涨到 350ms」 「这个月我们用了 60% 的错误预算」
「需要暂停功能开发」 「错误预算快用完了,建议先投一周在稳定性上」
「加索引能把 P99 降 40%」 「能把错误预算的消耗速度降低一半」

为什么这很重要:错误预算让性能工作可以和业务方用同一套语言沟通——它把「技术指标」翻译成了「可靠性资源」。


五、一个完整的错误预算看板

-- ① 当前可用性(30 天窗口)
sum(rate(http_requests_total{status!~"5.."}[30d]))
  / sum(rate(http_requests_total[30d]))

-- ② 错误预算剩余(可用性 SLI)
1 - (
  (1 - sum(rate(http_requests_total{status!~"5.."}[30d])) / sum(rate(http_requests_total[30d])))
  / (1 - 0.999)
)

-- ③ 错误预算剩余(延迟 SLI:P99 > 200ms 的比例)
1 - (
  sum(rate(http_request_duration_seconds_bucket{le="0.2"}[30d]))
  / sum(rate(http_request_duration_seconds_count[30d]))
  / (1 - 0.999)
)

-- ④ 燃烧率(1 小时窗口)
(
  1 - sum(rate(http_requests_total{status!~"5.."}[1h])) / sum(rate(http_requests_total[1h]))
) / (1 - 0.999)

看板的三个必备面板:

面板 内容
错误预算剩余 百分比 + 趋势(30 天窗口)
燃烧率 1 小时 / 6 小时 / 1 天 三个窗口
按接口分解 哪个接口在消耗预算(用于定位)

六、三个常见错误

❶ 把「P99 超标就告警」当作 SLO 监控

# ❌ 瞬时超标就告警 → 天天误报
- alert: P99TooHigh
  expr: histogram_quantile(0.99, ...) > 0.2

问题:P99 是波动的,偶尔超标是正常的(它本来就是 99 分位)。用瞬时值告警会淹没你。

正确:用错误预算的燃烧率(见下一节)。

❷ SLO 没有负载前提

凌晨 3 点 QPS = 2,P99 = 15ms ✅ 达标
白天 QPS = 800,P99 = 210ms ❌ 超标

问题:低流量下的「达标」没有意义(样本太少,且系统没压力)。

正确:SLO 要加 QPS > X 的条件,或者在低流量时用慢请求计数代替百分位。

❸ 只看技术 SLI,不看业务 SLI

技术指标:P99 = 150ms ✅、错误率 0.01% ✅
业务指标:下单成功率从 99.5% 降到 97% ❌

问题:可能有一部分请求「技术上是成功的」但业务上失败了(比如返回了空数据、降级结果)。

正确:关键业务链路要有端到端的业务 SLI。


七、本节小结

  1. 错误预算 = 性能的流量套餐:SLO 99.9% → 每月 0.1% 的请求可以违约。
  2. 用途:决定「该不该继续发布新功能」——预算耗尽就冻结功能发布。
  3. SLI 采集的三个要求:低开销(可长期运行)、覆盖完整(含饱和度)、测量点明确。
  4. 生产 SLO 要简化到能自动计算,但负载前提不能丢。
  5. 业务 SLI 比技术 SLI 更接近真实价值——技术全绿但业务失败的情况真实存在。
  6. 错误预算让性能工作可以用业务语言沟通——这是它最大的组织价值。
  7. 三个常见错误:用瞬时 P99 告警、SLO 没有负载前提、只看技术不看业务。

八、自测

  1. SLO 是 99.95%(30 天窗口),每天 200 万请求。请算出错误预算(以请求数计),并说明「预算消耗 50%」意味着什么。
  2. 为什么「P99 超过 200 ms 就告警」是一种糟糕的告警设计?请说出两个理由,以及更好的替代方案。
  3. 一个服务的技术指标全绿(P99 = 150 ms、错误率 0.01%),但业务方反馈「用户投诉变多了」。请说出三种可能的原因。
  1. 计算:30 天 × 200 万 = 6000 万请求;错误预算 = 0.05% × 6000 万 = 3 万请求。「消耗 50%」的含义:已经有 1.5 万个请求违约(失败或超时)。接下来该做什么:① 如果这 50% 是在 29 天里慢慢消耗的 → 属于正常范围,可以继续发布;② 如果是在几小时内消耗的 → 燃烧率极高,必须立刻排查(并考虑冻结发布);③ 按照预算策略:消耗 50%–80% 属于「谨慎发布」(加强金丝雀观察),如果继续恶化到 80% 以上就要「只发布修复性变更」。关键:消耗量要结合消耗速度看——同样的 50%,用 29 天和用 2 小时是完全不同的两件事。
  2. 两个理由:① P99 本身就是波动指标——它是「99% 分位」,天然会有 1% 的请求超过它,而这个分位数值本身也在波动(尤其流量低时样本少,波动更大);用瞬时值告警会导致频繁误报,最终被忽略。② 它不反映影响的累积——单次超标 5 分钟可能完全不影响月度 SLO(0.1% 的预算)。反过来,持续轻度超标(比如 P99 长期 205 ms)可能迅速耗尽预算,但每次都"只超一点"而不会触发瞬时告警。更好的替代方案:① 错误预算燃烧率告警——用「预算消耗速度」而不是瞬时值(见第 8.5 节);② 双窗口多阈值——长窗口(1 小时)抑制误报 + 短窗口(5 分钟)保证灵敏,两个同时满足才告警;③ 饱和度告警(连接池 pending、队列深度、GC 停顿)作为领先指标——它们在延迟恶化之前就会触发;④ 低流量场景改用慢请求计数(“过去 10 分钟有超过 100 个请求慢于 500ms”)而不是百分位。
  3. 三种可能:① 部分用户的请求被降级——比如缓存未命中时返回了「简化版」数据(技术上 200 OK,业务上信息不全);② 特定用户群体的问题——技术指标是全局聚合的,可能掩盖了某类用户/某个地域/某台实例的严重问题(比如只有 1% 的用户受影响,但他们的 P99 是 3 秒);③ 问题在应用之外——比如前端渲染慢、第三方 SDK 卡顿、CDN 问题、或者是业务逻辑变了(比如默认排序变化导致用户找不到商品);④ 第四个可能:错误被"消化"了——比如重试机制让最终成功率看起来正常,但用户的等待时间被拉长了(多重试了几次);⑤ 第五个可能:技术 SLI 的测量点不对——比如测的是应用内延迟(不含网络与排队),而用户实际等了 800 ms。诊断方向:先按维度切分指标(按用户类型、地域、实例、接口),再看端到端的业务 SLI(下单成功率、转化率),最后考虑用真实用户监控(RUM)或追踪来看完整链路。