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