路线图

04 错误预算(Error Budget)

星辉 2026-07-02 阅读 6 min 1,119 字 路线图
04 错误预算(Error Budget) 封面

概述#

如果 SLI 是温度计的读数、SLO 是设定的目标温度,那 Error Budget(错误预算)就是"在触发警报之前,系统还允许偏差多少"。它是 SRE 体系中最重要的决策工具——回答"现在能不能发版""要不要停下新功能修稳定性"这类问题,不再靠吵架,而是靠数据。

Error Budget 是 SRE 相对传统运维最具革命性的创新。传统运维说"出事了别发版",SRE 说"还剩 8 分钟错误预算,你评估一下风险"。前者是立场之争,后者是数据之争。

一、Error Budget 的定义与计算#

基础公式#

text
Error Budget = 1 - SLO

以 SLO 99.9%(月度)为例:
Error Budget = 1 - 0.999 = 0.001 = 0.1%

换算为不可用时间(以错误率 100% 计算):
月度 Error Budget = 0.001 × 30天 × 24h × 60min
                  = 43.2 分钟

每小时允许消耗 = 43.2 / (30 × 24) = 0.06 分钟/小时
               ≈ 每小时 3.6 秒的错误时间

不同 SLO 对应的月度 Error Budget:

SLO每月允许不可用每天允许不可用每小时允许不可用
99%7.2 小时14.4 分钟36 秒
99.5%3.6 小时7.2 分钟18 秒
99.9%43.2 分钟1.44 分钟3.6 秒
99.95%21.6 分钟0.72 分钟1.8 秒
99.99%4.32 分钟0.144 分钟0.36 秒

注意:"允许不可用"是按 100% 不可用计算的。实际故障通常是部分降级,所以同样的预算可以容忍更长的"部分故障"时间。例如 99.9% 月度预算 43 分钟,如果一次故障只影响 10% 用户,那这次故障可以持续 430 分钟(7 小时)才耗尽预算。

核心概念:燃烧率(Burn Rate)#

单纯看"还剩多少 Error Budget"不够,还要看以多快速度在消耗

text
燃烧率 (Burn Rate) = 当前错误率 / (1 - SLO)
                    = 当前错误率 / Error Budget 比例

以 SLO 99.9%(Error Budget 比例 = 0.001)为例:

当前错误率燃烧率含义
0.1%1x正好以"预算速度"消耗,30天刚好耗尽
0.5%5x5倍速消耗,约6天耗尽月度预算
1%10x10倍速消耗,约3天耗尽月度预算
5%50x50倍速,约14.4小时耗尽月度预算
10%100x100倍速,约7.2小时耗尽月度预算
14.4%144x1小时消耗20%月度预算(约5小时耗尽),需立即响应

燃烧率让"问题严重性"脱离绝对错误率,变成"相对预算的消耗速度"。1% 错误率对 SLO 99% 的服务是 1x(正常),对 SLO 99.9% 的服务是 10x(紧急)。同一个数字在不同 SLO 下含义完全不同。

燃烧率告警的 Prometheus 配置见 Ch08-告警工程,本章聚焦概念和决策逻辑。

二、Error Budget 的决策模型#

核心决策表#

Error Budget 剩余发布策略告警响应工程优先级
> 50%正常发布常规响应功能开发优先
50% ~ 25%发布需 review加强关注功能与稳定均衡
25% ~ 10%仅修复性发布紧急响应稳定性优先
< 10%冻结所有非紧急发布全员关注全力修复
< 0(超支)全员修复 + Postmortem复盘并调整 SLO偿还技术债

这套决策表是 Error Budget 落地的核心——所有发布决策、工程优先级、告警响应都由这个表驱动,而不是靠主观判断。决策表的每个档位都有明确的触发条件和动作,避免争议。

"预算用尽 = 停发布"#

这是 Error Budget 机制最核心的约束,也是最具争议的实践。背后的逻辑:

传统模式:SRE 凭经验判断"这个服务最近不太稳,别发了"。开发觉得被拦,"就是一个 bug fix,风险很小"。两个角色基于主观判断博弈。

Error Budget 模式:约定好 SLO 和 Error Budget,Budget 还剩 40% 就正常发,掉到 5% 就冻结。规则透明、事前约定,没有人拦你——是数据在说话。

为什么这个机制有效#

  1. 事前约定 > 事后争议。所有人都知道规则,不是在事故现场临时决策。开发在规划版本时就能看到 Budget 余额,提前预判能不能发。
  2. 双向激励。开发团队有动力提高稳定性(稳定性好 → Budget 多 → 可以多发版)。SRE 团队有动力降低告警噪音(误报多 → Budget 莫名消耗 → 开发有意见)。
  3. 量化"风险"。不是"这个版本有风险",而是"剩余 Error Budget 能承受这个版本出错"。
  4. 形成闭环。Budget 耗尽 → 冻结发布 → 全员修稳定性 → 稳定性提升 → Budget 恢复 → 解冻。这是正反馈循环。
  5. 让"停下来修"成为合法选项。传统团队难以主动停发修稳定性("为什么要停?出事了吗?"),Budget 耗尽就是合法理由。

冻结策略的实施细节#

Budget 耗尽冻结发布不是"完全不发",而是分级:

  • 完全冻结:所有非紧急发布停止。紧急 = 修复正在影响用户的 bug、安全补丁、合规要求
  • 灰度限制:不允许灰度 > 50%,新功能灰度 ≤ 10%
  • Review 加严:所有发布必须 SRE Lead + 业务 Owner 双签
  • 自动回滚阈值收紧:原来 5% 错误率才回滚,现在 1% 就回滚

冻结期结束条件:Budget 恢复到 25% 以上,且 Postmortem 的 Action Items 完成度 ≥ 80%。

三、多窗口燃烧率告警(Multi-Window Multi-Burn-Rate)#

这是 Google SRE Workbook 推荐的 SLO 告警方法。解决了一个核心矛盾:

  • 单看短窗口(5分钟):抖动误报严重,毛刺就响
  • 单看长窗口(3天):响应太慢,故障已经持续一天才发现

双窗口 AND 逻辑#

text
告警条件:短窗口超标 AND 长窗口超标

短窗口(5min / 30min):确认"现在正在出问题"
长窗口(1h / 6h):确认"问题在持续,不是毛刺"

两者同时满足才触发告警——短窗口提高灵敏度,长窗口过滤噪音。

为什么用 AND 而不是 OR?OR 条件会因为短窗口抖动频繁触发;AND 条件要求两个窗口都确认问题,能滤掉瞬时毛刺。这是燃烧率告警的精髓——用窗口组合实现"既快又准"。

三档告警分级#

级别长窗口短窗口燃烧率阈值含义告警通道
P0 Critical1h5m> 14.4x1小时内消耗2%月度预算page(电话/钉钉)
P1 Critical6h30m> 6x6小时内消耗5%月度预算page(电话/钉钉)
P2 Ticket3d6h> 1x3天内消耗10%月度预算(慢烧,月底会超支)ticket(工单)

为什么是 14.4x?

text
1小时消耗2%月度预算:
  月度预算总量 = 100%
  1小时 = 1 / (30 × 24) = 1/720 月度时长
  正常燃烧速度(1x)= 100% / 720 = 0.139%/h
  要在1小时内烧2%,燃烧率 = 2 / 0.139 ≈ 14.4
  
直接记住:P0 = 14.4x,P1 = 6x,P2 = 1x

这些阈值不是拍脑袋的——它们对应"在多长时间内会耗尽多少预算"。P0 是"1 小时内烧 2%"(紧急),P1 是"6 小时内烧 5%"(重要),P2 是"3 天内烧 10%"(趋势,月底会超支)。对齐 Google SRE Workbook:P0、P1 两档快烧都 page,P2 慢烧走 ticket。

Prometheus Recording Rules(从 Error Budget 实战文章提取)#

燃烧率计算依赖多个时间窗口的比率运算,实时计算会让 Prometheus 查询超时。必须用 Recording Rules 预计算:

yaml
groups:
  - name: slo_recording_rules
    interval: 30s
    rules:
      - record: job:slo_errors_per_request:ratio_rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
          /
          sum(rate(http_requests_total[5m])) by (job)

      - record: job:slo_errors_per_request:ratio_rate30m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[30m])) by (job)
          /
          sum(rate(http_requests_total[30m])) by (job)

      - record: job:slo_errors_per_request:ratio_rate1h
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[1h])) by (job)
          /
          sum(rate(http_requests_total[1h])) by (job)

      - record: job:slo_errors_per_request:ratio_rate6h
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[6h])) by (job)
          /
          sum(rate(http_requests_total[6h])) by (job)

      - record: job:slo_errors_per_request:ratio_rate3d
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[3d])) by (job)
          /
          sum(rate(http_requests_total[3d])) by (job)

      # Error Budget 剩余量(百分比)
      - record: job:slo_error_budget_remaining:ratio
        expr: |
          1 - (
            sum_over_time(job:slo_errors_per_request:ratio_rate5m[30d])
            /
            count_over_time(job:slo_errors_per_request:ratio_rate5m[30d])
          ) / 0.001

Recording Rules 的命名约定遵循 level:metric:operations 格式:

  • job = 聚合维度
  • slo_errors_per_request = 指标含义
  • ratio_rate5m = 计算方式(比率 + 窗口)

告警规则(快速燃烧 P0 示例)#

yaml
- alert: HighErrorBudgetBurnRate
  expr: |
    (
      job:slo_errors_per_request:ratio_rate1h{job="payment-service"} > (14.4 * 0.001)
    )
    and
    (
      job:slo_errors_per_request:ratio_rate5m{job="payment-service"} > (14.4 * 0.001)
    )
  for: 2m
  labels:
    severity: critical
    team: payment
  annotations:
    summary: '{{ $labels.job }} Error Budget 快速消耗'
    description: |
      燃烧率约 14.4x(1 小时已烧掉月度预算的 2%)
      按此速度月度 Error Budget 约 2 天(≈50 小时)内耗尽
      Runbook: https://wiki.example.com/runbook/high-burn-rate
      Grafana: https://grafana.example.com/d/slo?var-job={{ $labels.job }}

告警 annotation 必须包含 Runbook 链接和 Grafana 链接——on-call 工程师收到告警后能一键跳到处置文档和可视化面板,不必再去找。

完整的告警规则和 Alertmanager 配置见 Ch08-告警工程。

多窗口告警的常见陷阱#

陷阱 1:忘记设置 for 参数

没有 for 的告警会在条件刚满足时立即触发,非常容易产生抖动误报。建议 P1 设 for: 2m,P2 设 for: 15m

陷阱 2:Recording Rules 的 interval 过长

如果 Recording Rule 的 interval: 5m,而告警 for: 2m,数据刷新不及时会产生奇怪行为。Recording Rule interval 应该 ≤ 告警 for 时间的一半。

陷阱 3:多窗口条件写 OR 而不是 AND

yaml
# 错误:OR 太宽松,短窗口抖动频繁触发
expr: |
  ratio_rate1h > 0.0144
  or
  ratio_rate5m > 0.0144

# 正确:AND,两个窗口都超标才告警
expr: |
  ratio_rate1h > 0.0144
  and
  ratio_rate5m > 0.0144

陷阱 4:SLO 基准值写死在告警表达式里

当 SLO 从 99.9% 调整为 99.95% 时,需要找到所有告警规则并更新。更好的做法是用 Helm values 或外部变量注入。

四、实际案例分析#

案例:SLO 违规复盘(摘自 A 旗舰项目故障复盘)#

某次发布 v2.3.4 后,服务可用性骤降:

时间事件
14:25发布 v2.3.4
14:30P1 告警触发,burn rate 超过 14.4x
14:35值班工程师响应
14:42确认是新版本问题,开始回滚
14:58回滚完成,服务恢复
16:45延迟影响彻底消除

根因:新版本引入 N+1 查询,高流量下数据库连接池耗尽。

Error Budget 消耗:单次事件消耗了月度预算的 68%。这意味着剩下的 30 天里只剩 32% 的 Budget,必须冻结非紧急发布,全员投入 DB 查询审查和改进。

如果没有 Error Budget 机制,这个 team 可能在第二周就继续发版——然后再次出问题。有 Error Budget,数据强制团队先还技术债。

复盘产出 Action Items:

措施负责人截止日期
添加数据库连接数监控告警张三2 周内
代码审查加入查询性能检查李四3 周内
引入 SQL 慢查询自动检测王五4 周内
发布流程加压测前置赵六6 周内

案例:持续慢烧的隐蔽性#

某服务的 SLO 是 99.9%。某月开始,错误率从 0.05% 缓慢上升到 0.15%——仍然远低于 1% 的"明显故障"阈值,但燃烧率已经从 0.5x 升到 1.5x。

  • 14.4x 阈值没触发(远低于)
  • 6x 阈值没触发(远低于)
  • 1x 阈值(P2 慢烧告警)触发了——但这个团队当初把 P2 错配成了"只看 Dashboard、不发通知"

结果月底发现 Budget 消耗 80%,但因为没有 P0/P1 告警、P2 又不主动通知,团队完全没意识到。这就是把慢烧告警配成 Dashboard-only 的"隐形风险"——没人主动看 Dashboard 就等于不存在。

解决方案(也是本章表格里的标准做法,对齐 Google Workbook):1x 慢烧告警应配成 ticket 级(进工单系统,不直接打扰但会被看到),而不是只放 Dashboard;每周健康度日报里再包含 P2 趋势项。

五、Error Budget 的常见误区#

误区 1:Error Budget 剩余多 = 可以随便发版#

错误。Error Budget 剩余 90% 只是说明可靠性达标,不代表可以不 review、不测试就发。质量门禁、Code Review、灰度发布这些流程不受 Error Budget 影响。Budget 衡量的是"出错后的容错空间",不是"出错的可能性"。

误区 2:Error Budget 耗尽了就该放宽 SLO#

错误。如果 SLO 频繁耗尽,说明 SLO 可能设得太紧,但应该在季度回顾时正式调整 SLO,而不是临时放宽。否则 Error Budget 失去约束力的那一刻,SRE 体系就崩塌了。

正确做法:Budget 耗尽 → 冻结发布 → 全员修稳定性 → 季度回顾时分析"为什么 Budget 总是不够",决定是调整 SLO 还是调整工程投入。

误区 3:只做错误率 SLO#

错误。用户感受到的"慢"和"错"同样影响体验。P99 延迟也应该有自己的 Error Budget。两个维度的 SLO 独立管理,独立告警,独立计算 Budget。

完整 SLO 体系应该有至少两个维度:

  • 可用性(错误率):99.9%
  • 延迟(P99 < 500ms):99%

两个 Budget 独立计算。任一 Budget 耗尽都触发对应策略。

误区 4:Recording Rules 的 interval 与告警 for 不匹配#

Recording Rule 的 interval 应该 ≤ 告警 for 时间的一半。如果 Recording Rule 5 分钟刷新一次,告警 for: 2m,数据刷新不及时会导致告警行为异常。

误区 5:所有服务用同一个 SLO#

错误。不同服务对用户的重要性不同,应该有不同的 SLO。核心支付 99.99%,辅助功能 99.9%,内部工具 99%——一刀切要么浪费(内部工具也要 99.99%),要么冒险(支付只给 99%)。

误区 6:Budget 计算只看月度#

错误。月度 Budget 衡量长期趋势,但对短时间内的紧急故障不敏感。一次 30 分钟的全站故障会消耗掉月度 Budget 的 70%,但月度数字看起来"还有 30%"——这具有误导性。需要同时看月度 Budget + 实时燃烧率。

六、Error Budget Dashboard 设计#

一个好的 Error Budget Dashboard 需要展示三层信息:

Row 1:当前状态(Stat Panel)#

  • 当前错误率
  • 当前燃烧率
  • Error Budget 剩余百分比

Row 2:燃烧曲线(Time Series)#

promql
# 各时间窗口燃烧率对比
job:slo_errors_per_request:ratio_rate1h{job="payment-service"} / 0.001
job:slo_errors_per_request:ratio_rate6h{job="payment-service"} / 0.001

# 告警阈值参考线
vector(14.4)  # P0 阈值
vector(6)     # P1 阈值
vector(1)     # P2 阈值

Row 3:Error Budget 剩余量(Gauge + Time Series)#

promql
# 剩余 Error Budget 百分比(Gauge,0-100%)
(1 - (
  sum_over_time(job:slo_errors_per_request:ratio_rate5m{job="payment-service"}[30d:5m])
  / count_over_time(job:slo_errors_per_request:ratio_rate5m{job="payment-service"}[30d:5m])
) / 0.001) * 100

颜色编码#

Error Budget 剩余量 Gauge 使用阈值着色:

  • 绿色:> 50%(健康)
  • 黄色:20%-50%(关注)
  • 橙色:5%-20%(告警)
  • 红色:< 5%(危险)

Dashboard 必须对全员公开,不能只 SRE 内部看。开发团队、业务团队、管理层都应该能随时看到当前 Budget 状态——这是 Error Budget 机制发挥约束力的前提。

实战要点#

  1. 先建 Recording Rules,再配告警。没有 Recording Rules 预计算的燃烧率告警会拖垮 Prometheus。
  2. Error Budget Dashboard 三步走:当前燃烧率(实时)→ 月度消耗曲线(趋势)→ 剩余预算仪表盘(一目了然)。
  3. Budget 告警区分通道:对齐 Google Workbook——P0(1h 快烧)与 P1(6h 中速烧)都是 page(都要立即响应),P2(3d 慢烧)=ticket(进工单,不直接打扰但必被看到,不要只放 Dashboard)。
  4. 冻结策略需要管理层背书。Error Budget 耗尽时冻结发布,必须是 CTO/VP 级别认可的团队政策,不能是 SRE 自己拍板。
  5. 配套 SLO 回顾机制。每月回顾 Error Budget 消耗情况:是误报导致的还是真故障?SLO 目标是否需要调整?
  6. Budget 超支要有明确流程。超支不是"惩罚",是触发 Postmortem + 季度 SLO 回顾的信号。
  7. P2 趋势告警必须有可见的出口。只放 Dashboard 不主动通知,等于没有——配日报或周报邮件。
  8. Budget 数据要可解释。每笔 Budget 消耗都要能追溯到具体事件(哪次故障、哪次发布),不能是"不知道为什么 Budget 没了"。

小结#

Error Budget 是 SRE 体系的核心创新——把"可靠性"从一个模糊概念变成一个精确的数字决策工具。它解决了一个传统运维无法解决的问题:如何让"停下来修稳定性"成为一个有数据支撑的合法决策

记住三个等式:Error Budget = 1 - SLO;Budget 耗尽 = 停发布;燃烧率告警 = 多窗口 AND 逻辑。理解了这一点,就理解了 SRE 和传统运维的本质区别。

Error Budget 真正落地需要组织文化的支撑:管理层愿意接受"数据说话"的约束,开发团队愿意为稳定性负责,SRE 团队愿意把决策权交给数据。这三件事任何一件做不到,Error Budget 都会沦为 dashboard 装饰。下一章我们讲怎么消除 Toil——SRE 工程时间的来源。