SLO 工程:错误预算、Burn Rate、多窗口多燃烧率告警
某团队定了 99.9% 可用性 SLO——30 天滚动窗口内允许 0.1% 的请求失败。双十一前一周,SLO Dashboard 上的预算线已经贴地:不是一次灾难性事故,而是 Q3 连续 12 次”小发布”——每次 p99 从 120ms 漂到 130ms,每次都没触发 CPU 告警,每次都没人打开 SLO 面板。直到大促前冻结发布窗口,才发现预算早已耗尽。
这个故事概括了 SLO 工程的核心挑战:定一个数字很容易,让这个数字驱动日常决策很难。SLO 的价值不在数字本身,在于它创建了一条从”业务可接受的不可用程度”到”工程团队每天的开发节奏”的完整决策链。
本文从 SLI 选择、错误预算计算、Burn Rate
告警到组织落地,给出可直接粘贴到 Prometheus 的 Recording
Rule 与 Alert Rule 模板。文中 Burn Rate 阈值来自 Google
Site Reliability Workbook 第 5 章(A
级来源);PromQL 在本地用 promtool check rules
校验语法(本环境未安装 promtool/Docker 拉取镜像失败,规则以
Workbook 原文为准,部署前请在目标 Prometheus
版本上验证)。
一、SLO 的概念框架
1.1 四个术语,四种用途
| 术语 | 英文 | 定义 | 谁关心 |
|---|---|---|---|
| SLI | Service Level Indicator | 对服务某维度的实测值 | SRE、开发 |
| SLO | Service Level Objective | SLI 在窗口内应满足的目标 | 工程 + 产品 |
| SLA | Service Level Agreement | 违反 SLO 时的合同后果 | 法务、客户 |
| 错误预算 | Error Budget | \(1 - \text{SLO}\),窗口内允许的不合格量 | TL、PM、SRE |
SLI 例子:“过去 5 分钟,checkout API 成功请求占比 99.93%”。
SLO 例子:“30 天滚动窗口内,checkout 成功率 ≥ 99.9%”。
SLA 例子:“若季度可用性 < 99.5%,按合同退还 10% 月费”。SLO 通常比 SLA 更严——给自己留缓冲。
错误预算 不是”可以浪费的 downtime”。它是发布节奏控制器:预算充裕时承担发布风险;预算紧张时冻结 feature,只做可靠性工作。
1.2 错误预算的数量级
SLO 每多一个 “9”,预算缩小 10 倍:
| SLO | 30 天窗口允许失败比例 | 等价 downtime(连续不可用) | 等价失败请求(假设均匀流量) |
|---|---|---|---|
| 99% | 1% | 7.2 小时 | 每 100 请求 1 次失败 |
| 99.9% | 0.1% | 43.2 分钟 | 每 1000 请求 1 次失败 |
| 99.99% | 0.01% | 4.32 分钟 | 每 10000 请求 1 次失败 |
| 99.999% | 0.001% | 26 秒 | 每 100000 请求 1 次失败 |
选 SLO 之前先算一遍:这个窗口内我们客观上能容忍多少失败? 很多团队拍 99.99%,却从未验证过能否在现有架构下达到——结果要么永远超预算,要么 SLO 形同虚设。
1.3 SLO 与监控的区别
指标体系设计 中的 USE/RED/Golden Signals 回答”该采集什么”。SLO 回答”什么程度算不可接受、何时该停发版”。
监控告警常见反模式:CPU > 80% 就
Page——CPU 高不一定伤害用户。SLO Burn Rate
告警与用户可感知失败等价:只有正在加速消耗错误预算时才值得叫醒人(详见
告警体系)。
二、SLI 的选择与定义
2.1 一个服务 2–4 个 SLI 足够
SLI 过多等于没有 SLI——没人能同时盯 20 条曲线。Google SRE Workbook 建议按服务类型选 2–4 个:
| SLI 类型 | 适用服务 | 典型定义 | 常见误区 |
|---|---|---|---|
| 可用性 | 在线 API | 非 5xx 且非超时的请求 / 总请求 | 把 429 算进失败 |
| 延迟 | 同步 API | p99 延迟 < 阈值 | 用均值代替分位数 |
| 吞吐 | 批处理/队列 | 单位时间处理条数 ≥ 目标 | 与可用性 SLI 重复 |
| 持久性 | 存储/DB | 写入后可读且未丢失的比例 | 与备份 RPO 混淆 |
| 新鲜度 | 数据管道 | 数据滞后 < N 分钟 | 用批处理完成率代替 |
2.2 可用性 SLI:什么算”坏请求”
Google SRE Book 第 4 章:可用性 SLI = 好事件 / 合法事件。
# 好请求:HTTP 2xx/3xx/4xx 中排除"服务责任"之外的码
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
必须排除的请求类型:
- 429 Too Many Requests:通常是客户端或网关限流,不是服务端故障。
- 401/403(视产品而定):认证/授权失败可能是预期行为,不应计入服务可用性。
- 499 / 客户端超时取消:若由客户端主动断开,是否计入需与产品对齐——默认建议排除。
必须纳入的失败:
- 5xx:服务端错误。
- 超时:服务端未在 deadline 内响应——即使用户看到”网络错误”,也是服务 SLI 失败。
# 更严格的可用性 SLI(5xx + 超时)
sum(rate(http_requests_total{status=~"5.."}[5m]))
+
sum(rate(http_request_duration_seconds_count{le="+Inf"}[5m])
- rate(http_request_duration_seconds_count{le="2"}[5m]))
第二条假设 SLO 延迟阈值为 2s——超时请求计入不可用。阈值应与 SLO 文档一致。
2.3 延迟 SLI:必须用分位数
用 p50 做延迟 SLI 会在尾部退化时完全无感。SLO 延迟 SLI 标准写法:
\[\text{SLI}_{\text{latency}} = \frac{\#\{r \mid \text{latency}(r) < T\}}{\#\{r\}}\]
Prometheus histogram 实现(假设 bucket 上限覆盖 SLO 阈值 \(T\)):
sum(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
上式表示”p99 以下”若 bucket 为 0.2s——实际应用用
histogram_quantile 做报告,用
bucket 比例做SLI 合规判断:
# 过去 5 分钟 p99 是否 < 200ms(用于 Dashboard,非直接 SLI 合规)
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
) < 0.2
SLI 合规应使用窗口内低于阈值请求占比,而非单点 p99——后者对样本量敏感。
2.4 测量窗口:1 分钟 vs 5 分钟 vs 1 小时
| 窗口 | 优点 | 缺点 | 用途 |
|---|---|---|---|
| 1m | 反应快 | GC/抖动噪声大 | 不推荐单独做 SLO 窗口 |
| 5m | 平衡 | — | SLI 采集、Recording Rule 默认 |
| 1h | 平滑 | 检出慢 | Burn Rate 短窗口告警 |
| 30d 滚动 | 与预算对齐 | 计算复杂 | SLO 合规、预算剩余 |
Google SRE Workbook 推荐:SLI 用 1–5 分钟滚动窗口采集,SLO 合规用 30 天滚动窗口,告警用 1h/6h/3d 多窗口 Burn Rate。
2.5 SLI 采集的三条工程路径
路径 A:应用内 Prometheus 指标(最常见)
// Go + prometheus client:按 status 计数
httpRequestsTotal.WithLabelValues(method, path, strconv.Itoa(status)).Inc()路径 B:OpenTelemetry Span Metrics
OpenTelemetry Collector spanmetrics
connector 从 trace 生成 RED 指标——与 OpenTelemetry
深入 中的 pipeline 一致。适合已全量 OTel
但未在每个服务手工埋 counter 的团队。
路径 C:Synthetic / Black-box 探测
从用户视角探测 /health 或关键 API——与 Events 与变更关联 中的
black-box 互补。缺点:覆盖路径有限,不能替代服务端 SLI。
三、错误预算的工程计算
3.1 基本公式
30 天滚动窗口,SLO = 99.9%:
\[\text{ErrorBudget}_{\text{requests}} = (1 - 0.999) \times N_{30d}\]
\[\text{ErrorBudgetRemaining} = 1 - \frac{\text{BadRequests}_{30d}}{(1 - \text{SLO}) \times N_{30d}}\]
若流量均匀,43.2 分钟连续不可用 ≈ 耗尽 30 天预算(99.9%)。
3.2 滚动窗口 vs 日历窗口
| 类型 | 行为 | 适用 |
|---|---|---|
| 日历月 | 每月 1 日重置 | 与财务/合同对齐 |
| 滚动 30d | 任意时刻看过去 30 天 | 更平滑,Google 推荐 |
Prometheus 原生不内置”滚动 30 天 sum”——常用近似:
# 30 天失败请求(increase 近似)
sum(increase(http_requests_total{status=~"5.."}[30d]))
大窗口 increase() 在 Prometheus 2.x
可用,但长窗口 query 昂贵——生产环境用
Recording Rule 每 5 分钟预聚合。
3.3 Burn Rate 定义
燃烧率(Burn Rate)= 当前错误率相对于”刚好耗尽预算”的错误率之比。
\[\text{BurnRate} = \frac{\text{error\_rate}}{\text{SLO\_error\_rate}} = \frac{\text{error\_rate}}{1 - \text{SLO}}\]
- Burn Rate = 1:按当前速度,将在窗口结束时刚好耗尽预算。
- Burn Rate = 14.4:当前速度是”刚好耗尽”速度的 14.4 倍。
推导 14.4 的由来(Google SRE Workbook):30 天 = 720 小时。若 1 小时内以 14.4 倍速度烧预算,则消耗 \(\frac{14.4}{720} = 2\%\) 的 30 天总预算。这是 Page 级告警的敏感度/误报率折中。
3.4 Recording Rules 模板
以下规则假设:job 标签 checkout,SLO
99.9%,指标
http_requests_total{job="checkout"}。
# slo-recording-rules.yml
groups:
- name: slo_checkout_availability
interval: 30s
rules:
# 5 分钟错误率
- record: slo:checkout:errors5m
expr: sum(rate(http_requests_total{job="checkout",status=~"5.."}[5m]))
- record: slo:checkout:total5m
expr: sum(rate(http_requests_total{job="checkout"}[5m]))
- record: slo:checkout:error_ratio5m
expr: slo:checkout:errors5m / slo:checkout:total5m
# Burn Rate(相对 SLO 错误率 0.001)
- record: slo:checkout:burnrate5m
expr: slo:checkout:error_ratio5m / 0.001
- record: slo:checkout:burnrate1h
expr: |
sum(rate(http_requests_total{job="checkout",status=~"5.."}[1h]))
/ sum(rate(http_requests_total{job="checkout"}[1h]))
/ 0.001
- record: slo:checkout:burnrate6h
expr: |
sum(rate(http_requests_total{job="checkout",status=~"5.."}[6h]))
/ sum(rate(http_requests_total{job="checkout"}[6h]))
/ 0.001
- record: slo:checkout:burnrate3d
expr: |
sum(rate(http_requests_total{job="checkout",status=~"5.."}[3d]))
/ sum(rate(http_requests_total{job="checkout"}[3d]))
/ 0.001
# 30 天预算剩余(百分比,近似)
- record: slo:checkout:budget_remaining_ratio
expr: |
1 - (
sum(increase(http_requests_total{job="checkout",status=~"5.."}[30d]))
/ (0.001 * sum(increase(http_requests_total{job="checkout"}[30d])))
)将 0.001 替换为 \((1 - \text{SLO})\) 即可适配其他
SLO 目标。
四、多窗口多燃烧率告警
Google SRE Workbook 第 5 章给出 6 条规则——3 条 Page、3 条 Ticket。核心思想:短窗口 + 高燃烧率 抓突发;长窗口 + 低燃烧率 抓慢性退化。
4.1 Page 级规则(3 条)
| 规则 | 窗口 | 燃烧率阈值 | 预算消耗 |
|---|---|---|---|
| A | 1h | ≥ 14.4× | 2% / 30d |
| B | 6h | ≥ 6× | 5% / 30d |
| C | 3d | ≥ 1× | 10% / 30d(持续) |
# slo-alert-rules.yml — Page 级
groups:
- name: slo_checkout_pages
rules:
- alert: CheckoutSLOBurnRateCritical1h
expr: slo:checkout:burnrate1h > 14.4
for: 2m
labels:
severity: page
service: checkout
annotations:
summary: "checkout SLO 1h 燃烧率 {{ $value | printf \"%.1f\" }}×"
description: "1 小时内以当前速度将消耗约 2% 月错误预算。见 Runbook: ..."
- alert: CheckoutSLOBurnRateCritical6h
expr: slo:checkout:burnrate6h > 6
for: 5m
labels:
severity: page
service: checkout
- alert: CheckoutSLOBurnRateCritical3d
expr: slo:checkout:burnrate3d > 1
for: 1h
labels:
severity: page
service: checkoutfor: 子句用于抑制瞬时尖峰——Workbook 建议
Page 规则加 2–5 分钟 pending。
4.2 Ticket 级规则(3 条)
| 规则 | 窗口 | 燃烧率阈值 | 预算消耗 |
|---|---|---|---|
| D | 6h | ≥ 1× | 10% / 30d |
| E | 1d | ≥ 1× | 10% / 30d |
| F | 3d | ≥ 1× | 10% / 30d |
- name: slo_checkout_tickets
rules:
- alert: CheckoutSLOBurnRateWarning6h
expr: slo:checkout:burnrate6h > 1
for: 15m
labels:
severity: ticket
service: checkout
- alert: CheckoutSLOBurnRateWarning1d
expr: |
sum(rate(http_requests_total{job="checkout",status=~"5.."}[1d]))
/ sum(rate(http_requests_total{job="checkout"}[1d]))
/ 0.001 > 1
for: 1h
labels:
severity: ticket
- alert: CheckoutSLOBurnRateWarning3d
expr: slo:checkout:burnrate3d > 1
for: 3h
labels:
severity: ticketTicket 告警进入 Jira/Slack,不 打值班电话。
4.3 敏感性与噪音的权衡
flowchart LR
subgraph fast [快速检出]
A1["1h 窗口"] --> A2["燃烧率 14.4×"]
A2 --> A3["Page"]
end
subgraph slow [慢性退化]
B1["3d 窗口"] --> B2["燃烧率 1×"]
B2 --> B3["Ticket"]
end
反模式:短窗口 + 低燃烧率(如 5m + 2×)→ 每次 GC 都可能触发 → 告警疲劳(参见 告警体系 中”Page 只用于 Burn Rate”原则)。
反模式:长窗口 + 高燃烧率(如 3d + 14.4×)→ 几乎永不触发 → 假安全感。
4.4 Alert 标签与 Runbook 契约
每条 SLO Alert 应携带统一 labels,供 Alertmanager 路由:
labels:
severity: page # page | ticket
service: checkout
slo: availability # availability | latency
team: team-checkout
runbook_url: https://wiki.example/runbooks/checkout-slo
annotations:
summary: "{{ $labels.service }} {{ $labels.slo }} burn rate critical"
description: |
1h burn rate = {{ $value | printf "%.2f" }}× (threshold 14.4×).
Dashboard: https://grafana.example/d/checkout-slo
最近变更: 见 Panel 7 annotationsrunbook_url 最低内容:
- 打开 Grafana SLO Dashboard,确认 Burn Rate 窗口(1h/6h/3d)
- 检查最近 30min 部署(Panel 7)
- 若错误率上升:查 Traces/Logs 采样(存储成本 事故态采样)
- 是否 rollback:对照 Error Budget Policy §5.4 对话 3
- 升级条件:15min 无缓解 → TL;30min → VP Eng
无 runbook 的 Page 规则不应合并进生产——Alertmanager 只能路由,不能替人决策。
五、错误预算的策略使用
5.1 预算分区与发布策略
| 剩余预算 | 工程策略 | 产品策略 |
|---|---|---|
| > 50% | 正常发布节奏 | 可安排大促/实验 |
| 20%–50% | 冻结 feature,只允许 bugfix | 推迟非关键需求 |
| < 20% | 全员可靠性改进 | 取消 risky 发布 |
| 0% | 事故复盘 + SLO 复审 | 对外沟通(若触 SLA) |
策略必须书面化(Error Budget Policy 文档),明确谁有权 override——通常是 VP Eng + SRE Lead 联合批准,且 override 事件本身要审计。
5.2 预算”冷藏”(Freezing)
大促、春节、重大发布前 2–4 周 进入冷藏期:
- 禁止非紧急发布
- 提前完成依赖变更
- 预扩容、预跑混沌(见 混沌工程)
冷藏不是”不计预算”——而是降低消耗速度,把预算留给大促当天的已知风险。
5.3 预算耗尽之后
预算耗尽 ≠ 立即违约 SLA。它触发的是工程流程:
- 冻结所有 feature 发布直至预算恢复(滚动窗口自然回升或新窗口开始)
- 强制事故复盘:是 SLO 定太紧、还是系统真的不可靠?
- 更新 SLO 或投资可靠性——不要悄悄改 SLO 数字而不复盘
5.4 预算谈判:工程与产品的三条对话
对话 1:能否加 feature?
- 工程:剩余预算 35%,Burn Rate 6h 为 0.8×——可以,但大改需 TL 知悉
- 工程:剩余 15%,3d Burn Rate 1.2×——建议否,先修可靠性
对话 2:能否降低 SLO?
- 产品:99.9% 太严,想改 99.5%
- 工程:用 §1.2 展示预算从 43.2min/月 → 3.6h/月;问”用户是否接受更多可见故障”
- 输出:要么投资可靠性达标,要么书面降低 SLO 并更新 SLA 缓冲
对话 3:事故后是否继续发布?
- 若事故消耗 30% 预算但根因已修复且 Burn Rate 已 normalize → EBP 通常允许 小步发布 + 加强 canary
- 若根因未明、Burn Rate 6h 仍 > 1× → 冻结直至 Ticket 关闭
三条对话的共同点:用 Dashboard Panel 1 和 Panel 2 的截图作为会议事实来源,而非口头”感觉还行”。
5.5 与 告警体系 的分工
| 机制 | 回答的问题 |
|---|---|
| SLO / Burn Rate | 用户是否正在受损?预算烧多快? |
| Alertmanager route | 谁被叫醒、通过什么渠道? |
| Ticket 规则 | 慢性问题谁跟进、何时不打扰 on-call? |
SLO 规则产出 severity: page|ticket
label;Alertmanager 按 label 路由——两篇文章的 YAML 应
同仓库、同 CI 审查。
六、与数据库层 SLI 的映射
服务 SLO 失败往往根因在 DB。本站 PG 监控体系 从内核机制推导六个监控维度——本节说明如何把 DB 指标映射为上游服务的 SLI 输入,而非另建一套孤立的数据库 SLO。
6.1 分层 SLO 模型
flowchart TB
USER[用户请求] --> API[API 服务 SLO]
API --> DB[(PostgreSQL / MySQL)]
API --> CACHE[Redis]
DB --> DBSLI[DB 层 SLI]
DBSLI --> API
- 用户 facing SLO:checkout API 99.9%——唯一应对外承诺的。
- DB 层 SLI:连接可用性、查询 p99、复制 lag——解释 API SLO 失败的内部原因。
6.2 PG 维度 → 服务 SLI 映射表
| PG 监控维度(21-monitoring) | 内核故障模式 | 对 API SLI 的影响 | 是否应用作 Page |
|---|---|---|---|
idle in transaction 过长 |
阻止 VACUUM、持锁 | 延迟 SLI 退化 | 否(Ticket,除非已 Burn Rate) |
| 复制 lag 超阈值 | WAL 堆积 | 读从库失败 → 可用性 SLI | 否(DB 团队 Ticket) |
| XID wraparound 逼近 | DB 只读 | 可用性 SLI 100% 失败 | 是(若影响用户 SLO) |
pg_stat_statements 计划突变 |
慢查询 | 延迟 SLI | 通过 API Burn Rate |
原则:Page 只打用户 SLO Burn Rate。DB 指标发 Ticket 给 DBRE——除非 DB 故障已反映在 API Burn Rate 上。
6.3 mysqld / PG exporter 指标示例
# PG:连接使用率 > 85% → Ticket(非 Page)
sum(pg_stat_activity_count) / pg_settings_max_connections > 0.85
# MySQL:Innodb_buffer_pool 命中率骤降 → 调查延迟 SLI 根因
rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])
/
(
rate(mysql_global_status_innodb_buffer_pool_read_requests[5m])
+ rate(mysql_global_status_innodb_buffer_pool_reads[5m])
) < 0.95
DB 层告警应链到 性能调查方法论 的五层工具链,而不是独立叫醒 API on-call。
6.4 gRPC 与 HTTP 的 SLI 统一
混合协议栈中,checkout 对外 HTTP、对内 gRPC。若只对 HTTP 设 SLO,gRPC 5xx 可能通过内部调用链间接烧伤 HTTP SLI——但 gRPC 自身也应有内部 SLI 便于分解。
gRPC 可用性 SLI(Prometheus grpc-exporter 或 OTel):
sum(rate(grpc_server_handled_total{grpc_code!="OK",grpc_service="payment.Payment"}[5m]))
/
sum(rate(grpc_server_handled_total{grpc_service="payment.Payment"}[5m]))
grpc_code 映射注意:
| gRPC code | 是否计入 SLI 失败 | 说明 |
|---|---|---|
| OK | 否 | |
| Canceled | 通常否 | 客户端取消,类比 499 |
| InvalidArgument | 视产品 | 客户端错误,常排除 |
| DeadlineExceeded | 是 | 服务未在 deadline 内完成 |
| Unavailable | 是 | 依赖不可用 |
| Internal | 是 |
Dashboard 上应用 同一 Burn Rate
公式,仅替换指标名;Recording Rule 建议独立 group
slo_payment_grpc 避免 PromQL 过长。
6.5 新鲜度 SLI(数据管道类服务)
ETL、CDC、搜索索引构建等服务不适合用”请求成功率”——用 数据滞后(Freshness):
\[\text{SLI}_{\text{fresh}} = \mathbb{1}[\text{now} - \text{last\_successful\_batch\_ts} < L]\]
# 最后成功批处理距今秒数
time() - pipeline_last_success_timestamp_seconds{job="search-indexer"}
SLO 示例:99% 的 5 分钟窗口内滞后 < 300s。Burn Rate 定义为”滞后超阈值的时间占比 / (1 - SLO)“——需自定义 Recording Rule,不能照搬 HTTP 模板。
6.6 持久性 SLI(存储类)
对象存储、备份系统常用 durability SLI:写入后 N 天内可读取的比例。工程上常通过 定期校验 job(随机抽样 GET + checksum)生成:
sum(rate(durability_check_success_total[1d]))
/
sum(rate(durability_check_total[1d]))
持久性 SLO 窗口常取 90d 滚动而非 30d——与 存储工程 中的 RPO/RTO 概念对齐,但数值必须单独与业务/legal 确认。
七、SLO 文化与组织落地
7.1 谁定 SLO
| 角色 | 职责 |
|---|---|
| 开发/架构 | 提出 SLI 候选、实现埋点 |
| 产品 | 确认”用户何时感觉坏”→ 阈值 |
| SRE | Recording Rule、告警、Dashboard、复盘 |
| 管理层 | 批准 Error Budget Policy |
7.2 Dashboard 设计原则
SLO Dashboard 的第一受众是 PM 和 TL,不是 SRE:
- 大字:剩余错误预算百分比
- 趋势线:30 天 Burn Rate
- 标注:最近一次发布、变更事件(链 Events)
- 禁止:只有 SRE 能看懂的 PromQL 原始值
7.3 月度 SLO 评审会议程
- SLI 数据质量:有无采集断点?故障期间 Prometheus 是否可用?
- 上窗口 SLO 达标情况:超预算原因分类(发布/依赖/容量/代码 bug)
- 告警噪音回顾:哪些 Ticket 从未 action?哪些 Page 误报?
- SLO 数字是否仍与业务匹配——不是”只能调严”
7.4 Grafana 面板完整规格
SLO Dashboard 建议独立
Folder(SLO / Production),与基础设施
Dashboard 分离——PM 不应在 CPU 曲线里找预算。
下面以 checkout-api(SLO 99.9% 可用性 + 99%
请求 < 200ms)为例,列出 8 个 Panel
的查询、可视化与阈值。Panel JSON 可导入 Grafana 9+;变量
$service、$slo_target 在 Dashboard
Settings 中定义。
Dashboard 变量
| 变量 | 类型 | 取值示例 |
|---|---|---|
$service |
query | label_values(http_requests_total, job) |
$slo_target |
custom | 0.999(可用性)、0.99(延迟占比) |
$window |
custom | 30d |
Panel 1:错误预算剩余(Stat)
- 目的:PM 第一眼看到”还能不能发版”
- 查询:
slo:checkout:budget_remaining_ratio * 100
- Unit:percent (0–100)
- Thresholds:> 50 green,20–50 yellow,< 20 red
- 说明文字(Panel description):剩余预算 < 20% 时 Error Budget Policy 要求冻结 feature 发布
Panel 2:30 天 Burn Rate 趋势(Time series)
- 查询(三条线):
slo:checkout:burnrate1h
slo:checkout:burnrate6h
slo:checkout:burnrate3d
- Legend:
1h/6h/3d - Reference lines:y=14.4(Page A 阈值)、y=6(Page B)、y=1(Ticket / Page C)
- Y 轴:linear,min=0,建议 max=20(避免单次事故拉爆纵轴时可改用 log scale)
Panel 3:当前 SLI 可用性(Gauge + Stat)
1 - slo:checkout:error_ratio5m
- Thresholds:<
$slo_targetred,≥$slo_targetgreen - Subtitle:
5m 滚动窗口;非 30d 合规
Panel 4:延迟 SLI——低于 200ms 请求占比(Gauge)
sum(rate(http_request_duration_seconds_bucket{job="$service",le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count{job="$service"}[5m]))
- Thresholds:< 0.99 yellow,≥ 0.99 green
Panel 5:30 天失败请求累计(Bar gauge)
sum(increase(http_requests_total{job="$service",status=~"5.."}[$window]))
- 与预算允许失败数并排展示(Panel 6)
Panel 6:预算允许失败数 vs 已消耗(Stat pair)
# 允许(30d)
0.001 * sum(increase(http_requests_total{job="$service"}[$window]))
# 已消耗
sum(increase(http_requests_total{job="$service",status=~"5.."}[$window]))
- 两 Stat 并排;已消耗 / 允许 > 1 时整行标红
Panel 7:最近变更事件(Annotations)
- 数据源:Grafana Annotation API 或 Loki
{app="argocd"} |= "synced"日志 - 用途:Burn Rate 尖峰与部署时间对齐——复盘时回答”是不是刚发版”
- 与 Events
与变更关联 中的 CloudEvents
字段对齐:
deployment.id、service.name
Panel 8:活跃告警(Alert list)
- Filter:
alertname=~".*SLOBurnRate.*", service="$service" - 链接:每条告警
runbook_url指向 wiki
flowchart TB
subgraph row1 [第一行:决策]
P1[预算剩余 Stat]
P3[可用性 Gauge]
P4[延迟 SLI Gauge]
end
subgraph row2 [第二行:趋势]
P2[Burn Rate 1h/6h/3d]
end
subgraph row3 [第三行:归因]
P7[变更 Annotations]
P8[活跃告警]
end
P1 --> P2
P2 --> P7
反模式:把 30 天 increase()
直接放在 Dashboard 主查询——每次刷新全量扫描
TSDB,大集群会拖慢 Grafana。应使用 §3.4 的 Recording Rule
预聚合。
7.5 promtool 规则校验与 CI 门禁
Recording Rule 与 Alert Rule 的 PromQL 在 Prometheus 升级、指标 rename 后容易静默失效(查询成功但返回空)。Google SRE Workbook 建议在 CI 中对规则做语法检查与固定输入/output 单元测试。
本环境未安装
promtool(which promtool
无结果),以下配置来自 Prometheus
官方文档(A 级),部署前在目标 Prometheus
版本上执行。
步骤 1:语法检查
promtool check rules slo-recording-rules.yml slo-alert-rules.yml步骤 2:单元测试(slo_test.yml
摘录)
# slo_test.yml — promtool test rules
rule_files:
- slo-recording-rules.yml
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'http_requests_total{job="checkout",status="200"}'
values: '0+100x60'
- series: 'http_requests_total{job="checkout",status="500"}'
values: '0+0x59 100'
alert_rule_test:
- eval_time: 60m
alertname: CheckoutSLOBurnRateCritical1h
exp_alerts:
- exp_labels:
severity: page
service: checkout
exp_annotations:
summary: 'checkout SLO 1h 燃烧率'input_series 中 0+100x60 表示从
0 起每分钟 +100(模拟稳定 QPS);第 60 分钟注入 100 次 5xx
模拟事故尖峰——具体是否触发 Page 取决于
burnrate1h 表达式与 for:
窗口,需在本地 promtool test 调参后固化
expected。
步骤 3:GitHub Actions 片段
# .github/workflows/prometheus-rules.yml
jobs:
prom-rules:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download promtool
run: |
VER=2.51.0
curl -sL "https://github.com/prometheus/prometheus/releases/download/v${VER}/prometheus-${VER}.linux-amd64.tar.gz" | tar xz
echo "$(pwd)/prometheus-${VER}.linux-amd64" >> $GITHUB_PATH
- run: promtool check rules observability/slo-recording-rules.yml
- run: promtool test rules observability/slo_test.yml门禁策略:
| 变更类型 | CI 要求 |
|---|---|
| 修改 Recording Rule | check rules + 至少 1 条
test rules |
| 修改 Alert Rule | 同上 + Alertmanager amtool check-config(见
告警体系 附录
K) |
| 升级 Prometheus minor | 全量 replay test;关注 increase() 与
histogram 行为变更 |
坑:promtool test 的
eval_time 与 for: 叠加——Alert 的
for: 2m 意味着 test 中 eval_time
必须晚于条件首次满足至少 2 分钟,否则
exp_alerts 为空导致 CI 误报失败。
八、案例复盘:预算如何在沉默中耗尽
8.1 案例 A:Q3 十二次”小发布”(慢性退化)
背景:某电商 checkout-api,SLO 99.9%(30 天滚动),Error Budget Policy 已文档化但无人看 Dashboard。
时间线:
| 时间 | 事件 | 5m 错误率 | 1h Burn Rate | 预算剩余(约) | 为何未 Page |
|---|---|---|---|---|---|
| W1 | 发布 v2.3.1,依赖库升级 | 0.05% | 0.5× | 92% | < 14.4× |
| W3 | 发布 v2.3.2,连接池默认值变更 | 0.08% | 0.8× | 85% | Ticket 未配置 |
| W5–W12 | 每周一次 hotfix | 0.06–0.12% | 0.6–1.2× | 降至 8% | 6h/3d Ticket 规则缺失 |
| W13 大促前 | PM 要求冻结 | — | — | 8% | 人工发现 |
根因(机制):
- 每次发布引入 p99 +10ms 与 0.02% 额外 5xx——单独看都不触发 CPU/内存告警
- 只配置了 1h Page(14.4×),未配置 6h/3d Ticket(1×)——慢性退化无信号
- 429 曾被算进失败请求,客户端 retry 放大错误率(后按 §2.2 排除)
修复动作:
- 补齐 Workbook 6 条规则(§4.1–4.2)
- Grafana Panel 1(预算剩余)接入 TL 周报 Slack 截图
- 发布流水线增加:若
budget_remaining < 30%则 CI 警告(不阻断,但需 TL ack)
教训:SLO 工程的主要敌人不是单次事故,而是低于 Page 阈值、高于可持续错误率的持续泄漏。
8.2 案例 B:依赖 Redis 单点(突发 + 正确 Page)
背景:同一 checkout-api,payment 会话缓存走 Redis Cluster。
时间线:
| 时刻 | 现象 | API SLI | 1h Burn Rate | 响应 |
|---|---|---|---|---|
| T+0 | Redis 主节点网络分区 | 5xx 从 0.01% → 8% | 18× | Page A 触发 |
| T+3min | 值班 ack,切本地降级 | 5xx → 2% | 9× | Page B 仍 firing |
| T+15min | Redis 恢复 | 5xx → 0.02% | 0.3× | Page resolve |
做对的事:
- Page 仅基于 checkout Burn Rate,不是
redis_up——避免 DB/缓存团队与 API 团队重复叫醒 - Runbook 第一步:Grafana Panel 7 看是否刚发版(本次无)→ Panel 2 看 Burn Rate 窗口 → 依赖 trace 定位 Redis
做错的事(事后复盘):
- 降级路径未在混沌演练中验证,T+0–T+3min 仍返回 5xx 而非降级响应——错误预算在恢复前已烧 2.1%
- Redis 团队 Ticket 阈值(lag/memory)与 API SLO 未在联合评审中对齐
8.3 案例 C:Prometheus 与业务同挂(SLI 盲区)
背景:单 AZ Prometheus,与 checkout 同 K8s 集群。
事故:AZ 网络故障,checkout
不可用,Prometheus scrape
也失败——up{job="checkout"}==0,可用性
SLI 分母为零,Burn Rate 查询返回 NaN,无
Page。
修复:
- 双 Prometheus + Thanos Query 联邦(见 §9.5、Prometheus HA)
- 独立 black-box 探测(附录 C)在 不同 AZ
采集
probe_success - Alert:
absent_over_time(up{job="checkout"}[10m])→ 基础设施 Page,与 SLO Page 分离
8.4 从案例归纳的检查清单
| 检查项 | 案例 A | 案例 B | 案例 C |
|---|---|---|---|
| 6 条 Burn Rate 规则齐全 | 否 | 是 | 是 |
| Ticket 规则接 Slack/Jira | 否 | 是 | — |
| 预算剩余 Dashboard 有人看 | 否 | 是 | 是 |
| SLI 采集与业务故障域隔离 | — | — | 否 |
| 429/401 已从 SLI 排除 | 否 | 是 | 是 |
| Runbook 链到依赖排查 | — | 部分 | — |
九、工程坑点
9.1 选 SLO 不算预算
99.9% vs 99.99%:预算差 10 倍。上线前用表格(§1.2)算一遍。
9.2 用均值代替分位数
p50 正常、p99 崩溃——用户投诉的是 p99。延迟 SLI 必须基于分位数或”低于阈值请求占比”。
9.3 429 与 500 混算
429 是限流,500 是故障。混算会让恶意客户端或错误 retry 策略烧干预算。
9.4 短窗口 + 低燃烧率 = 告警风暴
每 5 分钟一条 Slack → 值班麻木 → 真正 Page 被忽略。
9.5 监控在故障中不可用
Prometheus 与业务同机房同故障域——业务挂时 SLI 也停采。双活采集、合成探测、跨 region remote write 至少选一种(见 Prometheus HA)。
9.6 每个团队各自定义 SLO 无法相加
平台级 SLO 需要从用户路径聚合,不能简单平均各微服务 SLO——需用错误预算的联合概率模型或取关键路径最弱环节。
十、落地清单
十一、关键概念回顾
- SLI → SLO → SLA:测量 → 目标 → 合同
- 错误预算:\(1 - \text{SLO}\),发布节奏控制器
- Burn Rate:\(\text{error\_rate} / (1 - \text{SLO})\);14.4×/1h ≈ 烧 2% 月预算
- 多窗口多燃烧率:短窗高率 Page,长窗低率 Ticket
- Page 条件:用户 SLO Burn Rate,不是 CPU/内存
十二、下一步
SLO 定好之后,需要告警体系承载 Burn Rate——Alertmanager route/inhibit、PagerDuty 排班、分级模板。下一篇 告警体系:Alertmanager、PagerDuty、分级抑制。
上一篇:内核追踪:ftrace、kprobe、uprobe、tracepoint 生产实战
下一篇:告警体系:Alertmanager、PagerDuty、分级抑制
参考资料
- Google, Site Reliability Engineering, Chapters 4–5, O’Reilly, 2016
- Google, The Site Reliability Workbook, Chapter 2 “Implementing SLOs” & Chapter 5 “Alerting on SLOs”, O’Reilly, 2018
- Prometheus, Recording Rules, https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/
- Prometheus, Alerting Rules, https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/
- OpenTelemetry, Span Metrics, https://opentelemetry.io/docs/collector/span-metrics/
附录 A:多服务 SLO 与依赖
微服务架构中,checkout 依赖 payment、inventory、user 三个下游。用户 facing SLO 失败时,需要知道哪条依赖链在烧预算。
A.1 关键路径 SLO
只对用户关键路径设 SLO,不对每个内部服务都设 99.99%:
| 服务 | 是否用户 facing | 建议 SLO |
|---|---|---|
| checkout-api | 是 | 99.9% |
| payment-internal | 否 | 99.5%(内部) |
| legacy-report | 否 | 99%(内部) |
内部服务 SLO Violation 通过 Ticket 处理,除非已传播到 checkout Burn Rate。
A.2 依赖 SLI 的 RED 指标传递
下游 payment 的
http_requests_total{status=~"5.."} 应带
caller="checkout" label(基数可控时)——checkout
团队能分解自身 SLI 失败中有多少来自 payment。
附录 B:延迟 SLO 的完整 PromQL 示例
假设 checkout p99 SLO:99% 请求 < 200ms(30 天窗口)。
# Recording:5 分钟窗口内"好延迟"请求占比
sum(rate(http_request_duration_seconds_bucket{job="checkout",le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count{job="checkout"}[5m]))
Burn Rate 对延迟 SLI:将分子分母中的”错误”定义为
le="0.2" 之外的请求:
(
1 - (
sum(rate(http_request_duration_seconds_bucket{job="checkout",le="0.2"}[1h]))
/ sum(rate(http_request_duration_seconds_count{job="checkout"}[1h]))
)
) / (1 - 0.99)
此处 SLO 为 99% 请求 < 200ms,故 \((1 - \text{SLO}) = 0.01\)。
附录 C:合成探测 SLI
Black-box 探测适合无侵入验证用户路径:
# prometheus/blackbox_exporter 模块
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_status_codes: [200]
fail_if_not_ssl: trueprobe_success{job="blackbox-checkout"}
合成 SLI 与服务器 SLI 取交集报告——合成失败但服务器指标正常,通常是 DNS/CDN/网络问题。
附录 D:SLO 与变更事件的关联
每次部署打 CloudEvents 到 Loki 或
Prometheus loki 不适用时用 ALERTS
关联:
# 部署后 15 分钟内 Burn Rate 上升 → 自动关联变更
slo:checkout:burnrate5m
and on()
increase(deployment_timestamp[15m]) > 0
Grafana Annotation 来自 CI webhook 是更常见的工程实现。
读完这篇,下一步读什么
优先读同系列或同问题的下一篇,把单篇消费变成主题集群。
【可观测性工程】告警体系:Alertmanager、PagerDuty、OnCall 与分级抑制
建一套不让人崩溃的告警体系。从 Prometheus Alertmanager 的分组/抑制/静默三元组,到 PagerDuty 排班与升级策略,到分级告警的设计模板与告警质量持续治理。
【系统架构设计】SLO 工程:可靠性的量化管理
SLI、SLO、SLA 不只是运维指标——它们是架构决策的定量依据。本文从 Google SRE 的 Error Budget 策略出发,拆解多窗口燃烧率告警的数学原理,讲清楚 SLO 如何在产品与工程的冲突中充当仲裁者,并给出基于 Prometheus 和 Grafana 的落地方案。
【可观测性工程】真实事故复盘剧本:从指标抖动到根因的全链路追查
虚构但可复现的 checkout 服务事故全链路:SLO Burn Rate 告警后按 Golden Minute→Metrics→Traces→Logs→Profile→Events 五阶递进排障,含 PromQL/LogQL/kubectl 命令与三条分级剧本,交叉引用系列 01–22。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。全 25 篇。