# SLO 与燃烧率告警 · 从根上解决阈值问题

> 错误预算把「可靠性」变成一种可以花的预算。这一章讲清 SLI/SLO/错误预算三件套、燃烧率（burn rate）的定义与那张经典的多窗口告警表，以及为什么它同时解决了误报、漏报和低流量三个难题——附可直接抄的规则。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-10-01
- 原文：https://daiw.org/manual/observability/slo-burn-rate
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# SLO 与燃烧率告警 · 从根上解决阈值问题

[上一章](https://daiw.org/manual/observability/alert-thresholds)讲了四种定阈值的方法，但都有点将就。**有没有一种方法能从根上解决？**

有。它来自 Google SRE，叫**基于错误预算燃烧率的告警**。这是现代告警工程的**最佳实践**，也是本部分最有价值的一章。

## 三件套：SLI / SLO / 错误预算

<Callout type="info">
  **先把三个词分清——它们经常被混用，但含义完全不同：**

  | 词 | 是什么 | 例子 |
  | --- | --- | --- |
  | **SLI**（指标） | **你测的那个数** | 成功请求数 / 总请求数 |
  | **SLO**（目标） | **你给自己定的目标** | SLI ≥ 99.9%（按 30 天滚动窗口） |
  | **错误预算** | **1 − SLO，你被允许失败的额度** | 0.1% = [30 天里约 43 分钟](https://daiw.org/manual/observability/metric-ranges) |
  | SLA（协议） | 对**外**的合同，违约要赔钱 | 通常比 SLO 宽松 |

  **`SLI` 是事实，`SLO` 是目标，`错误预算` 是两者之差。** 注意 SLO **必须**带时间窗口（“30 天内 99.9%”），否则无意义。
</Callout>

## 关键思维转变：预算是用来花的

<Callout type="info">
  **错误预算最反直觉、也最有价值的一点：**它不是“底线”，而是“额度”——你**应该**把它花掉。**

  传统思维：“**任何故障都是失败，我们要追求零故障。**”
  错误预算思维：“**我们被允许在 30 天内故障 43 分钟。没用完的预算是浪费的。**”

  为什么“没用完是浪费”？因为**可靠性是有成本的**，而且[成本随着九的增加指数上升](https://daiw.org/manual/observability/metric-ranges)：

  - 如果你**长期只用掉 10% 的预算**，说明你**过度投资**了可靠性——那些工程时间本可以拿去**做新功能**。
  - 预算**充足时，应该更激进**：更快地发布、多做实验、多搞混沌工程。
  - 预算**快烧完时，才该踩刹车**：冻结发布、专心修稳定性。

  **这把“要不要发布”这个永恒的争吵，从“拍脑袋 / 谁嗓门大”变成了一个**数据驱动的决策**：**
  ```
  错误预算还剩 60% → 发!
  错误预算还剩 5%  → 冻结,先修
  ```
  **这是 SRE 最重要的文化贡献：它让“稳定性”和“速度”从对立变成了一个可量化的权衡。** 而且它给了开发和运维**同一个数字**去争论，而不是各说各话。
</Callout>

## 燃烧率：预算烧得多快

<Callout type="info">
  **燃烧率（burn rate）= 当前错误率 / 错误预算率**。它的物理意义是“**按这个速度，预算会被用完得多快**”。

  ```
  SLO = 99.9%  →  错误预算率 = 0.1%

  当前错误率 0.1%  →  燃烧率 = 1    → 30 天正好烧完(符合预期)
  当前错误率 1%    →  燃烧率 = 10   → 3 天就烧完(!)
  当前错误率 10%   →  燃烧率 = 100  → 7.2 小时烧完(!!)
  当前错误率 0.01% →  燃烧率 = 0.1  → 300 天才烧完(过度可靠)
  ```

  **燃烧率 = 1 意味着“刚好按计划消耗”。> 1 就是在超支。**

  **这个定义的美妙之处：它把“错误率多少算严重”这个问题，变成了“**预算多久会烧完**”——而后者有**明确的、可行动的**含义。** 燃烧率 14.4 意味着“**2 天的预算会在 1 小时内烧光**”，这显然该叫醒人；燃烧率 1.5 意味着“会略微超支”，开个工单就行。

  **阈值不再需要拍脑袋——它由“你愿意在多久内烧掉多少预算”推导出来。**
</Callout>

## 多窗口多燃烧率：那张经典的表

<Callout type="info">
  **《SRE Workbook》给出的方案（业界事实标准）：**用几组不同的窗口 + 燃烧率，兼顾“快速发现严重问题”和“不漏掉慢性问题”**。**

  | 严重度 | 燃烧率 | 长窗口 | 短窗口 | 含义 | 动作 |
  | --- | --- | --- | --- | --- | --- |
  | **紧急** | **14.4** | 1h | 5m | **2 天预算会在 1 小时内烧光** | **page** |
  | **紧急** | **6** | 6h | 30m | 2 天预算在 6 小时内烧光 | **page** |
  | 一般 | **3** | 1d | 2h | 10% 预算 1 天内烧掉 | ticket |
  | 一般 | **1** | 3d | 6h | 慢性劣化 | ticket |

  **为什么要“长窗口 + 短窗口”两个条件同时满足？**

  - **长窗口** —— 判断“**问题足够严重/持久**”。它防**误报**：一次抖动不会让 1 小时的燃烧率飙到 14.4。
  - **短窗口** —— 判断“**问题还在持续**”。它让告警**快速恢复**：问题解决后，5 分钟窗口立刻降下来，告警就消了；如果只看 1 小时窗口，**问题修好后告警还要挂 1 小时**。

  **两个窗口是一对：长的防误报，短的保证快速恢复。** 这比 [`for` 子句](https://daiw.org/manual/observability/alerting-rules)更精细——`for` 只能延迟触发，而短窗口能**加速恢复**。

  **为什么燃烧率越高、窗口越短？** 因为**严重程度和紧迫性成正比**：烧得越快，越该早发现（1 小时窗口）；烧得慢的（燃烧率 1），你有几天时间反应，可以用 3 天窗口慢慢确认——**这样既不会漏掉“慢性失血”，也不会为它半夜叫人**。
</Callout>

## 可以直接抄的规则

<Callout type="info">
  **配合[上一章的 recording rules](https://daiw.org/manual/observability/recording-rules)（多窗口错误率已预聚合好）：**

  ```yaml
  groups:
  - name: slo-alerts
    rules:
    # 紧急:14.4 倍燃烧率(1h 且 5m 都超标)
    - alert: ErrorBudgetBurnFast
      expr: |
        (
          service:http_errors:ratio_rate1h > (14.4 * 0.001)
          and
          service:http_errors:ratio_rate5m > (14.4 * 0.001)
        )
      labels:
        severity: page
      annotations:
        summary: "{{ $labels.service }} 正在快速烧错误预算(2 天的预算 1 小时内烧光)"
        runbook_url: "https://wiki/runbooks/error-budget-burn"

    # 紧急:6 倍燃烧率(6h 且 30m)
    - alert: ErrorBudgetBurnMedium
      expr: |
        (
          service:http_errors:ratio_rate6h > (6 * 0.001)
          and
          service:http_errors:ratio_rate30m > (6 * 0.001)
        )
      labels:
        severity: page

    # 一般:3 倍燃烧率(1d 且 2h)→ 工单
    - alert: ErrorBudgetBurnSlow
      expr: |
        (
          service:http_errors:ratio_rate1d > (3 * 0.001)
          and
          service:http_errors:ratio_rate2h > (3 * 0.001)
        )
      labels:
        severity: ticket
  ```

  **注意 `0.001` 就是错误预算率（1 − 99.9%）。改 SLO 只需要改这个数。**

  **注意这里不需要 `for`** —— [长窗口本身已经起到了防抖作用](https://daiw.org/manual/observability/alerting-rules)（1 小时的平均错误率不会因为一次抖动就飙升）。**这是燃烧率方案的又一个优点：少一个要调的参数。**
</Callout>

## 它同时解决了三个难题

<Callout type="info">
  **回头看，燃烧率方案一口气解决了前面几章提到的所有痛点：**

  - **✅ 阈值不用拍脑袋** —— [由 SLO 和“愿意多久烧完”推导出来](https://daiw.org/manual/observability/alert-thresholds)。改 SLO，所有阈值自动跟着变。
  - **✅ 误报少** —— [长窗口天然防抖](https://daiw.org/manual/observability/alerting-rules)，不需要额外调 `for`。
  - **✅ 漏报少** —— [多个燃烧率覆盖了“急性”和“慢性”两类问题](https://daiw.org/manual/observability/slo-burn-rate)。传统单阈值告警**必然漏掉慢性失血**（错误率 0.3% 永远够不到 1% 的阈值，但它在稳定地烧预算）。
  - **✅ [低流量陷阱](https://daiw.org/manual/observability/alert-thresholds)自动解决** —— 燃烧率衡量的是**预算消耗速度**。深夜 2 个请求错 1 个，虽然错误率 50%，但**烧掉的预算绝对量极小**——用长窗口（1h）一平均，燃烧率根本上不去。**问题自然消失，不需要打补丁。**
  - **✅ 严重度自然分级** —— [燃烧率的大小直接对应紧迫性](https://daiw.org/manual/observability/alert-thresholds)，page 还是 ticket 一目了然。
  - **✅ 对症状告警** —— [SLI 天然是用户视角的症状](https://daiw.org/manual/observability/alert-thresholds)，不是内部原因。

  **一套机制解决六个问题，这就是为什么它是现代告警的最佳实践。**
</Callout>

## 落地建议

<Callout type="warn">
  **① 别一上来就给所有服务定 SLO**

  从**最关键的 1~2 个用户旅程**开始（比如“下单”、“登录”）。**SLO 应该覆盖“用户旅程”，不是“每个微服务”**——用户不在乎你的 auth 服务可用性，他在乎“能不能登录成功”。

  **② SLO 要和业务方达成一致**

  99.9% 还是 99.95%，这是个[商业决策](https://daiw.org/manual/observability/metric-ranges)（多一个九要多花多少钱？值吗？）。**技术团队单方面定的 SLO 没有意义**——因为“预算烧完要冻结发布”这个约束，必须是业务方也认的。

  **③ 先观察再执行**

  新定的 SLO 先**只看不告警**，跑几周，看真实的燃烧率长什么样。[如果基线就达不到 SLO](https://daiw.org/manual/observability/metric-ranges)，那不是告警问题，是**系统或 SLO 本身的问题**。

  **④ 别忘了延迟 SLO**

  [成功率只是一半](https://daiw.org/manual/observability/four-golden-signals)。“99.9% 的请求在 300ms 内成功”才是完整的 SLI——**慢和坏在用户眼里没区别**。延迟 SLI 通常表达为“快于阈值的请求占比”：
  ```text
  # 延迟 SLI:P99 < 300ms 等价于"99% 的请求 < 300ms"
  sum(rate(http_request_duration_seconds_bucket{le="0.3", status=~"2.."}[5m]))
  / sum(rate(http_request_duration_seconds_count{status=~"2.."}[5m]))
  ```
  **注意这里直接用了 `le="0.3"` 这个桶**——[所以桶边界必须正好在 SLO 阈值上](https://daiw.org/manual/observability/metric-ranges)！这是[第 2 部分那条“桶要在 SLO 阈值附近加密”](https://daiw.org/manual/observability/metric-ranges)的硬核理由：**没有 `le="0.3"` 这个桶，你根本算不出这个 SLI**。
</Callout>

## 小结与下一章

**SLI（事实）/ SLO（目标，必带时间窗口）/ 错误预算（1−SLO）**。关键思维转变：**预算是用来花的，不是底线**——[没用完说明过度投资了可靠性，那些工程时间本可做新功能；预算充足就激进发布，快烧完才踩刹车](https://daiw.org/manual/observability/metric-ranges)。**这让“稳定性 vs 速度”从争吵变成数据驱动的决策**。**燃烧率 = 当前错误率 / 错误预算率**，物理意义是“[预算多久烧完](https://daiw.org/manual/observability/slo-burn-rate)”——**阈值由此推导，不用拍脑袋**。**多窗口多燃烧率**（14.4/1h+5m → page;6/6h+30m → page;3/1d+2h → ticket）：[**长窗口防误报、短窗口保证快速恢复**，两者是一对；燃烧率越高窗口越短（严重度 ∝ 紧迫性）](https://daiw.org/manual/observability/alerting-rules)。它**一口气解决六个问题**：[阈值有依据、误报少（长窗口自带防抖，不用 `for`）、不漏慢性失血、低流量陷阱自动消失、严重度自然分级、天然对症状告警](https://daiw.org/manual/observability/alert-thresholds)。落地：[从关键用户旅程而非每个微服务开始、SLO 要业务方认可、先观察再执行、别忘延迟 SLO](https://daiw.org/manual/observability/four-golden-signals)——**延迟 SLI 直接用 `le="0.3"` 桶，这是“桶必须落在 SLO 阈值上”的硬核理由**。

告警规则写好了，但通知怎么送到人？下一章看 Alertmanager。👉 [Alertmanager](https://daiw.org/manual/observability/alertmanager)。

## 参考资料

- [1] Google《SRE Workbook》第 5 章「Alerting on SLOs」（多窗口多燃烧率表的来源）、《SRE Book》第 3-4 章。
- [2] 关联[阈值怎么定](https://daiw.org/manual/observability/alert-thresholds)、[几个九的含义](https://daiw.org/manual/observability/metric-ranges)、[recording rules 多窗口预聚合](https://daiw.org/manual/observability/recording-rules)。
- [3] Prometheus 官方文档：Alerting rules。
