概述

错误预算(Error Budget)是 SRE 体系中最精妙的机制设计。它把"稳定性 vs 迭代速度"这个长期依赖口水战的矛盾,转化为一个可量化的工程决策框架:你的系统有一个"不可用额度",花完了就得停下来修。

但实践中,很多团队定义了 SLO 和错误预算之后,就止步于仪表盘上展示一个百分比数字。预算耗尽时该怎么办?快耗尽时要采取什么行动?预算富裕时可以做什么?这些关键问题如果没有明确的策略,错误预算就只是一个好看的数字,而非真正驱动行为的工具。

详细梳理错误预算的消耗状态模型、每种状态对应的行动纲领、发布冻结的标准与流程、预算滚存与重置策略、跨团队协调机制,并配以实战案例。

本文假设读者已了解 SLI/SLO/错误预算的基本概念。如需补充,可参考 Google SRE Book - Embracing Risk 和本站 SRE核心理念:SLI、SLO与错误预算

一、错误预算的工程本质

不只是"还剩多少额度"

很多人把错误预算理解为"这个月还能宕机多少分钟"。这只是表面理解。错误预算的工程本质是:

错误预算是创新速度与系统稳定性之间的自动调节阀。

它回答了一个在所有工程团队都存在但很难回答的问题:我们现在应该更激进地发布新功能,还是应该停下来提升稳定性?

  • 预算充裕 → 系统足够稳定,可以承担更多变更风险 → 加速发布
  • 预告耗尽 → 系统已经接近可靠性边界 → 减速发布,专注稳定性

这个调节是自动的、基于数据的、不依赖个人判断和政治博弈的。

错误预算的计算

SLO = 99.9%(30天窗口)
错误预算 = (1 - SLO) × 时间窗口 = 0.1% × 43200 分钟 = 43.2 分钟/月

已消耗预算 = 实际不可用时间
剩余预算 = 43.2 - 已消耗时间
预算消耗率 = 已消耗预算 / 总预算

但"不可用"的判定不只是"服务完全宕机"。任何 SLI 违规都消耗预算:

# 错误预算消耗的多种形式
budget_consumption:
  # 完全不可用
  - type: downtime
    duration: 5min
    budget_cost: 5min

  # 延迟违规(SLO: P99 < 200ms,实际 P99 = 500ms)
  - type: latency_violation
    window: 10min          # 违规持续 10 分钟
    affected_requests: 50000
    budget_cost: 10min     # 按违规持续时间消耗

  # 错误率超标(SLO: 错误率 < 0.1%,实际错误率 = 2%)
  - type: error_rate_violation
    window: 3min
    affected_requests: 8000
    budget_cost: 3min

  # 数据不一致
  - type: correctness_violation
    window: 15min
    budget_cost: 15min

二、错误预算的四种消耗状态

状态模型

将错误预算的消耗程度划分为四个状态,每个状态对应不同的行动纲领:

  预算消耗率
  0% ─────────────────────────────────────────── 100%
     │         │              │            │
     │  🟢 绿  │    🟡 黄     │   🟠 橙   │  🔴 红
     │  安全区 │    警戒区    │  危险区   │  冻结区
     │  0-25%  │   25-50%    │  50-75%  │  75-100%
     │         │              │            │
     正常发布    关注但不限制    限制发布     冻结发布
状态消耗率含义核心行动
🟢 绿色(安全区)0-25%预算充裕,系统健康正常发布,鼓励创新和实验
🟡 黄色(警戒区)25-50%预算开始消耗,需要关注正常发布,但增加监控频率,排查消耗原因
🟠 橙色(危险区)50-75%预算消耗过快,有耗尽风险限制非必要发布,要求灰度发布,启动专项治理
🔴 红色(冻结区)75-100%预算即将或已经耗尽冻结功能发布,只允许稳定性修复,全员投入改进

消耗速率 vs 消耗量

状态判定不能只看"消耗了多少",还要看"消耗得多快"。同样是消耗了 30% 的预算:

  • 30 天均匀消耗 30% → 正常节奏,黄色状态即可
  • 3 天消耗了 30% → 消耗速率异常,应按橙色处理

引入消耗速率(Burn Rate) 指标:

消耗速率 = (预算消耗量 / 总预算) / (已过时间 / 总时间窗口)

示例:
  30天窗口,第3天消耗了 30% 预算
  消耗速率 = 0.30 / (3/30) = 0.30 / 0.10 = 3.0
  → 按当前速率,10天就会耗尽整个月的预算
消耗速率含义建议行动
< 1.0消耗速度正常,预算会持续到窗口结束维持正常节奏
1.0 - 2.0消耗略快于预期增加关注,排查异常消耗
2.0 - 5.0消耗明显过快限制发布,启动专项排查
> 5.0消耗极快,即将耗尽立即冻结发布,紧急介入

三、每种状态的行动纲领

🟢 绿色状态(0-25%):正常运营

预算充裕意味着系统当前足够稳定,可以承担更多变更风险。

行动纲领

  1. 正常发布节奏:不施加额外限制,鼓励团队按计划发布新功能
  2. 实验性变更:可以尝试新的架构方案、技术栈升级等有风险的改进
  3. 容量测试:可以进行压力测试、混沌实验等可能短暂影响可靠性的活动
  4. 预算投资:利用充裕的预算做"预防性投资"——技术债务清理、监控完善、自动化建设

注意事项

  • 绿色状态不意味着可以挥霍预算——故意消耗预算来"刷新"是不合理的
  • 如果长期处于绿色状态且剩余预算很多,可能说明 SLO 设定过于宽松,需要考虑收紧

🟡 黄色状态(25-50%):提高警觉

预算开始消耗,虽然还有余量,但需要理解消耗原因。

行动纲领

  1. 正常发布,但加强观察:不限制发布,但每次发布后密切关注 SLI 变化
  2. 消耗归因分析:分析预算消耗的来源——是计划内的维护窗口消耗,还是意外故障?
  3. 检查趋势:消耗速率是否在加速?如果持续加速,需要提前干预
  4. 通知相关团队:在 SRE 周报中通报预算消耗情况,让开发团队知晓

消耗归因分类

budget_consumption_analysis:
  total_consumed: 35%  # 43.2 分钟中已消耗 15.1 分钟

  breakdown:
    planned_maintenance:
      duration: 4min
      percentage: 26%
      description: "计划内的数据库维护窗口"

    unexpected_incidents:
      duration: 7min
      percentage: 47%
      incidents:
        - "07-05 配置错误导致 3 分钟部分不可用"
        - "07-08 缓存节点故障导致 4 分钟延迟劣化"

    deployment_related:
      duration: 4min
      percentage: 27%
      description: "3 次发布导致的短暂错误率升高"

  assessment: "消耗主要来自意外故障,需要关注配置变更管控"

🟠 橙色状态(50-75%):限制风险

预算消耗过半,剩余预算可能不足以支撑到窗口结束。必须采取措施减速。

行动纲领

  1. 限制非必要发布:只允许 P0 级别的功能发布和 bug 修复,推迟非紧急功能
  2. 强制灰度发布:所有变更必须经过灰度发布,从 1% → 10% → 50% → 100%
  3. 启动专项治理:成立专项小组排查预算消耗原因,制定恢复计划
  4. 增加防御措施:临时加强监控频率、调低告警阈值、准备回滚方案
  5. 管理层通报:向技术管理层通报预算状态和风险,争取资源支持

发布限制的具体措施

# 橙色状态发布策略
deployment_policy:
  allowed:
    - type: bug_fix
      severity: [P0, P1]
      requires: canary_release
    - type: security_patch
      severity: [P0, P1]
      requires: canary_release
    - type: stability_improvement
      requires: full_review

  restricted:
    - type: new_feature
      action: defer
      reason: "错误预算橙色状态,推迟到下个预算窗口"
    - type: architecture_change
      action: defer
      reason: "错误预算橙色状态,推迟到下个预算窗口"

  mandatory_controls:
    - canary_release: true       # 必须灰度
    - rollback_plan: true        # 必须有回滚方案
    - change_review: "senior"    # 需要 senior 级别 review
    - monitoring_watch: 30min    # 发布后观察 30 分钟

🔴 红色状态(75-100%):冻结发布

预算即将耗尽或已耗尽。这是最严肃的状态——系统已经逼近可靠性边界。

行动纲领

  1. 冻结所有功能发布:只允许安全补丁和稳定性修复,其他一切变更暂停
  2. 全员投入稳定性:开发团队和 SRE 团队联合投入,集中解决导致预算消耗的根因
  3. 启动事后复盘:对导致预算耗尽的故障进行深度复盘,确保根因被消除
  4. 管理层介入:技术管理层需要介入,协调跨团队资源,追踪改进项
  5. 用户沟通(如需要):如果用户已感知到可靠性下降,需要主动沟通

发布冻结的执行流程

预算进入红色状态
  → SRE 自动触发冻结通知(邮件 + IM + 看板)
  → CI/CD 管线自动拒绝非稳定性修复的发布请求
  → 开发团队负责人确认冻结范围
  → 每日 Standup 追踪稳定性改进进展
  → 预算恢复到橙色以下 → 解冻发布

冻结期间的发布例外审批

# 发布冻结期间的例外审批流程
freeze_exception:
  criteria:
    - "安全漏洞修复(CVE 高危以上)"
    - "正在影响用户的 bug 修复"
    - "防止预算进一步消耗的稳定性修复"

  approval_flow:
    1: "提交例外申请,说明原因和风险评估"
    2: "SRE 负责人审核技术可行性"
    3: "技术总监批准"
    4: "仍需灰度发布 + 密切监控"

  auto_reject:
    - "新功能发布"
    - "非紧急的 UI 优化"
    - "技术债务清理"
    - "依赖升级"

四、预算滚存与重置策略

预算窗口设计

错误预算的窗口长度直接影响行为模式:

窗口长度优势局限适用场景
7 天(滚动窗口)反应灵敏,快速发现问题噪音大,短期波动影响大高频发布的核心服务
30 天(日历月)与业务周期对齐,易于沟通可能"月初挥霍月末紧张"大多数服务的默认选择
90 天(季度窗口)平滑短期波动,关注趋势反应迟缓,问题发现太晚成熟稳定的基础服务

实践建议:主窗口用 30 天,辅助监控用 7 天滚动窗口的消耗速率

预算滚存

当一个预算窗口结束时,剩余预算是否可以滚存到下一个窗口?

策略说明优势局限
不滚存(重置)每个窗口重新开始简单清晰,避免预算囤积月末"突击花钱"的道德风险
部分滚存允许滚存最多 N%平衡灵活性和纪律性规则稍复杂
全额滚存剩余预算全额累积奖励稳定性好的团队可能导致预算囤积过多,SLO 失去约束力

推荐策略:部分滚存,上限 50%

新窗口预算 = 基础预算 + min(剩余预算, 基础预算 × 50%)

示例:
  基础预算 = 43.2 分钟/月
  上月剩余 = 20 分钟
  滚存额度 = min(20, 43.2 × 0.5) = min(20, 21.6) = 20 分钟
  本月预算 = 43.2 + 20 = 63.2 分钟

  另一种情况:
  上月剩余 = 35 分钟
  滚存额度 = min(35, 43.2 × 0.5) = min(35, 21.6) = 21.6 分钟
  本月预算 = 43.2 + 21.6 = 64.8 分钟

预算重置

以下情况需要重置(清零)错误预算:

  1. SLO 调整:当 SLO 目标值变更时,错误预算基数变化,需要重置
  2. 重大架构变更:系统架构发生根本性变化后,历史数据不再有参考意义
  3. 窗口自然结束:如果不采用滚存策略,每个窗口结束时自动重置

重置时的注意事项

  • 重置不等于"债务免除"——导致上一个窗口预算耗尽的根因仍然需要跟进
  • 重置后应在 24 小时内确认新的预算基线,并通知所有相关团队
  • 避免在窗口边界附近"赶着发布"——这种行为说明文化出了问题

计划内维护的预算处理

计划内维护(如数据库升级、架构迁移)会消耗预算,但这与意外故障性质不同:

策略一:额外预算

月度总预算 = 常规预算 + 维护预算
  常规预算 = 43.2 分钟(SLO 99.9%)
  维护预算 = 10 分钟(预先申请)
  总预算 = 53.2 分钟

策略二:维护窗口豁免

# 维护窗口期间的 SLI 计算
maintenance_window:
  start: "2026-07-15 02:00"
  end: "2026-07-15 04:00"
  slo_exclusion: true    # 此期间的 SLI 违规不计入错误预算
  conditions:
    - "必须提前 7 天公告"
    - "必须安排在低峰时段"
    - "单月维护窗口不超过 2 次"
    - "每次不超过 2 小时"

推荐策略二——维护窗口豁免,但严格管控豁免条件,防止滥用。

五、跨团队协调

依赖服务的预算联动

微服务架构中,服务 A 依赖服务 B。当服务 B 的故障导致服务 A 的 SLO 违规时,预算消耗应该怎么算?

场景:支付服务依赖用户服务
  用户服务故障 5 分钟 → 支付服务也间接不可用 5 分钟
  → 两个服务的错误预算都消耗了 5 分钟

原则:上游故障导致的下游预算消耗,由上游承担

# 预算消耗归因
budget_attribution:
  payment_service:
    total_consumed: 8min
    breakdown:
      self_caused: 3min        # 自身原因导致
      upstream_caused: 5min    # 上游依赖导致
        - user_service: 2min
        - inventory_service: 3min

  # 预算调整
  adjusted_budget:
    payment_service:
      original_budget: 43.2min
      upstream_transfer: 5min      # 上游转移的消耗
      effective_consumed: 3min     # 实际自身消耗
      effective_remaining: 40.2min

共享服务的预算分摊

基础设施服务(如数据库、消息队列)被多个业务服务共享。当基础设施故障时,多个业务服务同时受影响:

数据库故障 10 分钟 → 支付服务、订单服务、用户服务全部受影响
→ 三个服务各消耗 10 分钟预算
→ 但根因在数据库团队

协调机制

  1. 基础设施服务有自己的 SLO:数据库服务也需要定义 SLO 和错误预算
  2. 故障归因到基础设施:基础设施故障导致的下游消耗,记入基础设施的预算
  3. 升级机制:如果基础设施频繁导致下游预算消耗,触发基础设施团队的专项治理

预算协调会议

对于有复杂依赖关系的多服务系统,建议定期召开预算协调会议:

参会者:各服务 SRE 负责人 + 架构师
频率:每两周一次
议题:
  1. 各服务预算消耗状态
  2. 跨服务故障的归因和分摊
  3. 共享基础设施的预算消耗趋势
  4. 联合改进项的进展

六、实战案例

案例一:从绿色到红色的 72 小时

背景:某电商平台的支付服务,SLO 99.9%,月度预算 43.2 分钟。

Day 1(7月1日)

14:00  发布新支付渠道集成
14:15  灰度 10%,错误率正常
14:30  全量发布,一切正常
→ 预算消耗:0 分钟,状态:🟢 绿色

Day 2(7月2日)

02:00  新支付渠道的回调处理有 bug,在高并发下出现超时
       错误率从 0.05% 升至 0.8%
       持续 15 分钟后被告警发现
02:15  回滚发布
→ 预算消耗:15 分钟,状态:🟡 黄色(35%)

Day 3(7月3日)

10:00  修复 bug 后重新发布
10:30  另一个 unrelated 的配置变更导致数据库连接异常
       支付服务不可用 12 分钟
→ 预算消耗:15 + 12 = 27 分钟,状态:🟠 橙色(63%)

关键决策点

状态: 橙色(63%)
消耗速率: 27min / 3天 = 9min/天
按此速率: 43.2 - 27 = 16.2min 剩余,约 1.8 天后耗尽
→ 消耗速率 = 27/43.2 ÷ 3/30 = 0.625 ÷ 0.1 = 6.25(极高)

行动:
  1. 冻结所有非稳定性发布 ✅
  2. 启动根因分析 ✅
  3. 加强配置变更管控 ✅
  4. 管理层通报 ✅

结果:冻结发布 5 天,集中修复了支付渠道回调 bug 和配置变更流程。剩余预算 16.2 分钟在月末恰好用完,未突破 SLO。

经验教训:消耗速率比消耗量更能反映风险。如果只看"还剩 37%",可能会低估危险——实际上按当时的速率,不到 2 天就会耗尽。

案例二:预算富裕的正确用法

背景:某服务的 API 网关,SLO 99.95%,月度预算 21.6 分钟。连续 3 个月预算消耗低于 30%。

分析

budget_history:
  - month: "2026-04"
    consumed: 4min   # 18%
  - month: "2026-05"
    consumed: 6min   # 28%
  - month: "2026-06"
    consumed: 3min   # 14%

assessment: "连续 3 个月预算消耗低于 30%"

可能的解释

  1. SLO 设定过于宽松 → 考虑收紧到 99.99%
  2. 团队过于保守,不敢做有风险的变更 → 鼓励技术创新
  3. 系统确实非常稳定 → 利用富裕预算做预防性投资

决策

action_plan:
  1. 收紧 SLO:
     from: 99.95%
     to: 99.99%
     reason: "当前 SLO 无法有效约束行为,收紧后预算 = 4.3min/月"

  2. 利用当前稳定性窗口做技术升级:
     - API 网关从 Nginx 迁移到 Envoy
     - 引入 Service Mesh
     - 这些变更有风险,但预算充裕时是最佳时机

  3. 增加混沌工程实验:
     - 主动注入故障验证系统弹性
     - 可能短暂消耗预算,但能发现潜在风险

经验教训:预算长期富裕不是好事——要么 SLO 太松失去了约束力,要么团队过于保守失去了创新空间。错误预算的目标不是"不花钱",而是"把钱花在有价值的地方"。

七、错误预算的度量与告警

关键度量指标

# 错误预算剩余比例
1 - (
  sum(rate(http_requests_total{status=~"5.."}[30d]))
  /
  sum(rate(http_requests_total[30d]))
) / (1 - 0.999)

# 消耗速率(1小时窗口)
(
  sum(rate(http_requests_total{status=~"5.."}[1h]))
  /
  sum(rate(http_requests_total[1h]))
) / (1 - 0.999)

# 预计预算耗尽时间
remaining_budget_ratio /
(
  sum(rate(http_requests_total{status=~"5.."}[1h]))
  /
  sum(rate(http_requests_total[1h]))
) / (1 - 0.999)

多窗口告警策略

参考 Google SRE 的多窗口多消耗速率告警:

# 基于消耗速率的多窗口告警
groups:
  - name: error-budget-alerts
    rules:
      # 快速消耗:1小时内消耗了 2% 以上的月度预算
      - alert: ErrorBudgetFastBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total[1h]))
          ) > (1 - 0.999) * 14          
        for: 2m
        labels:
          severity: page
          budget_state: orange
        annotations:
          summary: "错误预算快速消耗(1小时窗口消耗速率 > 14x)"
          description: "按当前速率,约 2 小时内将消耗 30% 月度预算"

      # 慢速消耗:6小时内消耗了 5% 以上的月度预算
      - alert: ErrorBudgetSlowBurn
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
            /
            sum(rate(http_requests_total[6h]))
          ) > (1 - 0.999) * 6          
        for: 15m
        labels:
          severity: ticket
          budget_state: yellow
        annotations:
          summary: "错误预算持续消耗(6小时窗口消耗速率 > 6x)"
          description: "按当前速率,约 5 天内将耗尽月度预算"

告警设计原理

告警类型短窗口长窗口消耗速率目的
快速消耗1h-14x捕捉突发故障,立即响应
持续消耗-6h6x捕捉慢性问题,工单跟踪
预算耗尽-30d1x预算耗尽通知,触发冻结

八、常见误区与应对

误区一:“预算耗尽就是 SLO 不达标”

误区:认为错误预算耗尽等同于 SLO 违约,因此不敢消耗任何预算。

纠正:错误预算就是用来消耗的——只要不超额,消耗是正常的。零消耗反而说明 SLO 太松或团队太保守。

误区二:“预算耗尽只影响 SRE 团队”

误区:认为错误预算是 SRE 的事,开发团队不受影响。

纠正:预算耗尽导致的发布冻结直接影响开发团队的功能交付。错误预算必须是跨团队共享的约束机制——开发团队用预算来发布新功能,SRE 团队用预算来保障稳定性,双方都有责任管理预算。

误区三:“手动管理预算状态”

误区:依赖人工判断预算状态并决定是否冻结发布。

纠正:预算状态判定和发布冻结应该自动化。通过 CI/CD 管线集成预算检查,当预算进入红色状态时自动拒绝非稳定性发布的合并请求。

# CI/CD 管线中的预算检查
pre_deploy_check:
  - name: check-error-budget
    script: |
      BUDGET_STATE=$(curl -s $BUDGET_API/service/$SERVICE_NAME/state)
      if [ "$BUDGET_STATE" = "red" ] && [ "$DEPLOY_TYPE" != "stability_fix" ]; then
        echo "Error budget in RED state. Only stability fixes are allowed."
        exit 1
      fi
      if [ "$BUDGET_STATE" = "orange" ] && [ "$CANARY_ENABLED" != "true" ]; then
        echo "Error budget in ORANGE state. Canary release is mandatory."
        exit 1
      fi      

误区四:“所有服务用同一个 SLO”

误区:全公司统一用 99.9% 作为 SLO。

纠正:不同服务对业务的关键程度不同,SLO 应该分级设定:

服务等级SLO错误预算/月适用场景
L1 核心99.99%4.3 分钟支付、登录、核心 API
L2 重要99.95%21.6 分钟订单、搜索、推荐
L3 一般99.9%43.2 分钟报表、后台管理
L4 辅助99.5%216 分钟内部工具、文档

总结

错误预算的消耗策略是 SRE 体系从"概念"走向"行动"的关键环节。一个好的消耗策略应该回答以下问题:

  1. 当前预算状态是什么? → 四色状态模型,一目了然
  2. 每个状态该做什么? → 明确的行动纲领,不留模糊空间
  3. 什么时候冻结发布? → 75% 消耗或消耗速率 > 5x,自动化执行
  4. 预算怎么滚存? → 部分滚存,上限 50%,平衡灵活性和纪律性
  5. 跨团队怎么协调? → 归因到根因服务,共享基础设施独立 SLO

错误预算的本质不是一个"扣分机制",而是一个资源分配框架。它让团队在稳定性和创新之间做出有数据支撑的权衡——这才是 SRE 的工程精神。

最后提醒:错误预算策略需要与组织文化匹配。在初期推行时,可以从宽松的阈值开始(比如红色设为 90% 而非 75%),随着团队成熟度提升逐步收紧。重要的是建立"预算驱动决策"的习惯,而非一开始就追求完善的策略设计。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Google SRE Book - Embracing Risk — Google SRE 团队,参考了Google SRE Book - Embracing Risk相关内容