04 错误预算(Error Budget)
概述#
如果 SLI 是温度计的读数、SLO 是设定的目标温度,那 Error Budget(错误预算)就是"在触发警报之前,系统还允许偏差多少"。它是 SRE 体系中最重要的决策工具——回答"现在能不能发版""要不要停下新功能修稳定性"这类问题,不再靠吵架,而是靠数据。
Error Budget 是 SRE 相对传统运维最具革命性的创新。传统运维说"出事了别发版",SRE 说"还剩 8 分钟错误预算,你评估一下风险"。前者是立场之争,后者是数据之争。
一、Error Budget 的定义与计算#
基础公式#
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"不够,还要看以多快速度在消耗。
燃烧率 (Burn Rate) = 当前错误率 / (1 - SLO)
= 当前错误率 / Error Budget 比例以 SLO 99.9%(Error Budget 比例 = 0.001)为例:
| 当前错误率 | 燃烧率 | 含义 |
|---|---|---|
| 0.1% | 1x | 正好以"预算速度"消耗,30天刚好耗尽 |
| 0.5% | 5x | 5倍速消耗,约6天耗尽月度预算 |
| 1% | 10x | 10倍速消耗,约3天耗尽月度预算 |
| 5% | 50x | 50倍速,约14.4小时耗尽月度预算 |
| 10% | 100x | 100倍速,约7.2小时耗尽月度预算 |
| 14.4% | 144x | 1小时消耗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% 就冻结。规则透明、事前约定,没有人拦你——是数据在说话。
为什么这个机制有效#
- 事前约定 > 事后争议。所有人都知道规则,不是在事故现场临时决策。开发在规划版本时就能看到 Budget 余额,提前预判能不能发。
- 双向激励。开发团队有动力提高稳定性(稳定性好 → Budget 多 → 可以多发版)。SRE 团队有动力降低告警噪音(误报多 → Budget 莫名消耗 → 开发有意见)。
- 量化"风险"。不是"这个版本有风险",而是"剩余 Error Budget 能承受这个版本出错"。
- 形成闭环。Budget 耗尽 → 冻结发布 → 全员修稳定性 → 稳定性提升 → Budget 恢复 → 解冻。这是正反馈循环。
- 让"停下来修"成为合法选项。传统团队难以主动停发修稳定性("为什么要停?出事了吗?"),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 逻辑#
告警条件:短窗口超标 AND 长窗口超标
短窗口(5min / 30min):确认"现在正在出问题"
长窗口(1h / 6h):确认"问题在持续,不是毛刺"两者同时满足才触发告警——短窗口提高灵敏度,长窗口过滤噪音。
为什么用 AND 而不是 OR?OR 条件会因为短窗口抖动频繁触发;AND 条件要求两个窗口都确认问题,能滤掉瞬时毛刺。这是燃烧率告警的精髓——用窗口组合实现"既快又准"。
三档告警分级#
| 级别 | 长窗口 | 短窗口 | 燃烧率阈值 | 含义 | 告警通道 |
|---|---|---|---|---|---|
| P0 Critical | 1h | 5m | > 14.4x | 1小时内消耗2%月度预算 | page(电话/钉钉) |
| P1 Critical | 6h | 30m | > 6x | 6小时内消耗5%月度预算 | page(电话/钉钉) |
| P2 Ticket | 3d | 6h | > 1x | 3天内消耗10%月度预算(慢烧,月底会超支) | ticket(工单) |
为什么是 14.4x?
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 预计算:
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.001Recording Rules 的命名约定遵循 level:metric:operations 格式:
job= 聚合维度slo_errors_per_request= 指标含义ratio_rate5m= 计算方式(比率 + 窗口)
告警规则(快速燃烧 P0 示例)#
- 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
# 错误: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:30 | P1 告警触发,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)#
# 各时间窗口燃烧率对比
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)#
# 剩余 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 机制发挥约束力的前提。
实战要点#
- 先建 Recording Rules,再配告警。没有 Recording Rules 预计算的燃烧率告警会拖垮 Prometheus。
- Error Budget Dashboard 三步走:当前燃烧率(实时)→ 月度消耗曲线(趋势)→ 剩余预算仪表盘(一目了然)。
- Budget 告警区分通道:对齐 Google Workbook——P0(1h 快烧)与 P1(6h 中速烧)都是 page(都要立即响应),P2(3d 慢烧)=ticket(进工单,不直接打扰但必被看到,不要只放 Dashboard)。
- 冻结策略需要管理层背书。Error Budget 耗尽时冻结发布,必须是 CTO/VP 级别认可的团队政策,不能是 SRE 自己拍板。
- 配套 SLO 回顾机制。每月回顾 Error Budget 消耗情况:是误报导致的还是真故障?SLO 目标是否需要调整?
- Budget 超支要有明确流程。超支不是"惩罚",是触发 Postmortem + 季度 SLO 回顾的信号。
- P2 趋势告警必须有可见的出口。只放 Dashboard 不主动通知,等于没有——配日报或周报邮件。
- Budget 数据要可解释。每笔 Budget 消耗都要能追溯到具体事件(哪次故障、哪次发布),不能是"不知道为什么 Budget 没了"。
小结#
Error Budget 是 SRE 体系的核心创新——把"可靠性"从一个模糊概念变成一个精确的数字决策工具。它解决了一个传统运维无法解决的问题:如何让"停下来修稳定性"成为一个有数据支撑的合法决策。
记住三个等式:Error Budget = 1 - SLO;Budget 耗尽 = 停发布;燃烧率告警 = 多窗口 AND 逻辑。理解了这一点,就理解了 SRE 和传统运维的本质区别。
Error Budget 真正落地需要组织文化的支撑:管理层愿意接受"数据说话"的约束,开发团队愿意为稳定性负责,SRE 团队愿意把决策权交给数据。这三件事任何一件做不到,Error Budget 都会沦为 dashboard 装饰。下一章我们讲怎么消除 Toil——SRE 工程时间的来源。