土法炼钢兴趣小组的算法知识备份

【系统架构设计百科】SLO 工程:可靠性的量化管理

文章导航

分类入口
architecture
标签入口
#SLO#SLI#SLA#error-budget#SRE#burn-rate#reliability

目录

你的系统上线了,可用性”挺高的”。具体多高?“反正没怎么出过事。”上个月有没有故障?“好像有一次,持续了几分钟,影响不大。”影响了多少用户?“不太清楚。”

这段对话在无数团队中反复出现。系统稳定性的管理停留在”感觉”层面——感觉还行,感觉没事,感觉问题不大。当产品经理要求加一个高风险的新功能时,工程师说”可能影响稳定性”,产品经理说”上次你也这么说,结果没事”。争论的双方都没有数据支撑。

Google 的 SRE(Site Reliability Engineering,站点可靠性工程)团队在 2003 年就开始面对这个问题。他们的解决方案不是”让系统更稳定”——因为无限稳定的成本是无限的——而是给稳定性一个精确的数字,然后围绕这个数字构建决策框架

这个框架的核心是三个概念:SLI(Service Level Indicator,服务水平指标)、SLO(Service Level Objective,服务水平目标)和 SLA(Service Level Agreement,服务水平协议)。它们不是运维团队的内部指标,而是连接产品、工程、管理层的共同语言。

本文要回答三个问题:

  1. SLI / SLO / SLA 的精确定义是什么,它们之间的关系是什么?
  2. Error Budget(错误预算)如何把”稳定性 vs. 功能开发”从主观争论变成客量化决策?
  3. 怎么用多窗口燃烧率告警替代简单的阈值告警,减少告警噪声?

上一篇中,我们讨论了容灾架构——如何在灾难发生时保障业务连续性。SLO 工程是容灾的另一面:不是应对极端事件,而是管理日常的可靠性水位。


一、SLI / SLO / SLA:从指标到契约

SLI:服务水平指标

SLI 是对系统行为的一个可量化的度量。它必须是具体的、可测量的、有业务含义的。

好的 SLI 的特征:

常见的 SLI 类型:

SLI 类型 定义 适用场景
可用性(Availability) 成功请求数 / 总请求数 几乎所有在线服务
延迟(Latency) 请求处理时间的分布(通常取 p50、p99) 面向用户的 API
吞吐量(Throughput) 单位时间处理的请求或事件数 数据管道、批处理系统
正确性(Correctness) 返回正确结果的请求数 / 总请求数 搜索、推荐系统
新鲜度(Freshness) 数据更新到可查询之间的延迟 数据仓库、搜索索引
持久性(Durability) 成功存储的数据比例 存储系统

SLO:服务水平目标

SLO 是在 SLI 上设定的目标值。它表达的是:我们承诺这个 SLI 在某个时间窗口内达到某个水平。

例如:

SLO 的关键特征:

  1. 有时间窗口。99.9% 的可用性是每天?每周?每月?每个选择的含义完全不同。
  2. 低于 100%。SLO 永远不应该是 100%,因为 100% 的可靠性是不可能的,追求 100% 意味着不允许任何变更,这会杀死产品迭代。
  3. 是内部目标,不是外部承诺。SLO 是工程团队给自己设定的标准。

SLA:服务水平协议

SLA 是与外部客户签订的法律契约。如果服务未达到 SLA 约定的水平,服务提供方需要承担后果(通常是经济赔偿)。

SLA 通常比 SLO 宽松。例如:

SLO 和 SLA 之间的差距是一个”安全边际”。如果 SLO 和 SLA 设为相同值,那么一旦 SLO 被突破,就立刻触发赔偿——没有任何缓冲。

三者的关系

graph LR
    SLI["SLI<br/>指标:我们度量什么"] --> SLO["SLO<br/>目标:我们期望达到什么水平"]
    SLO --> SLA["SLA<br/>契约:未达标的后果是什么"]
    SLI --> |"成功请求/总请求"| M1["99.95%"]
    M1 --> SLO
    SLO --> |"安全边际"| SLA
    SLA --> |"赔偿条款"| C["客户补偿"]

二、选择好的 SLI

选择 SLI 是 SLO 工程中最关键的一步。选错了 SLI,后面的所有工作都是在错误的方向上努力。

基于请求的 SLI vs. 基于窗口的 SLI

基于请求的 SLI(Request-based SLI):每个请求独立判断是”好”还是”坏”,然后计算好请求的比例。

可用性 SLI = 状态码非 5xx 的请求数 / 总请求数

适用于请求-响应模式的在线服务(HTTP API、gRPC 服务等)。

基于窗口的 SLI(Window-based SLI):把时间切分为固定长度的窗口(例如每分钟),每个窗口判断是”好”还是”坏”,然后计算好窗口的比例。

可用性 SLI = 数据同步延迟 < 5 秒的分钟数 / 总分钟数

适用于后台服务、数据管道等非请求-响应模式的系统。

SLI 的度量点

同一个 SLI,在不同位置度量,结果可能截然不同。

用户浏览器 → CDN → 负载均衡 → API 网关 → 应用服务 → 数据库
   ①          ②       ③         ④          ⑤         ⑥

在位置 ⑤ 度量的延迟可能是 20 毫秒,但用户在位置 ① 感受到的延迟可能是 200 毫秒(加上 DNS 解析、TCP 握手、TLS 协商、网络传输、前端渲染等)。

原则:SLI 应该尽可能靠近用户度量。 如果用户通过 HTTP API 访问服务,那么在负载均衡器或 API 网关处采集的请求日志是最佳的度量点——它既靠近用户端,又在你的控制范围内。

避免虚荣指标

以下指标看起来有用,但作为 SLI 容易误导:


三、设定 SLO:不能太紧,不能太松

太紧的 SLO

如果 SLO 设为 99.999%(“五个九”),意味着每月只允许 26 秒的不可用时间。这意味着:

太松的 SLO

如果 SLO 设为 99%,意味着每月允许 7.2 小时的不可用时间。这意味着:

合理的 SLO 怎么定

  1. 从历史数据出发。过去 90 天,系统的实际可用性是多少?如果实际可用性是 99.95%,SLO 可以设为 99.9%——比实际稍低,留出合理的操作空间。
  2. 考虑用户期望。用户对延迟 200 毫秒和延迟 500 毫秒的感知差异很大,但对延迟 50 毫秒和延迟 100 毫秒的感知差异很小。SLO 应该反映用户真正关心的阈值。
  3. 考虑依赖方的 SLO。如果你的系统依赖一个 SLO 为 99.9% 的外部服务,你的系统的 SLO 不可能高于 99.9%(除非你有降级方案)。
  4. 逐步收紧。先从一个保守的 SLO 开始,积累数据后再逐步收紧。从 99.9% 收紧到 99.95% 远比从 99.99% 放松到 99.95% 容易被团队接受。

不同可用性等级的停机时间参考

SLO 可用性 年度允许停机时间 月度允许停机时间 每日允许停机时间
99%(两个九) 3.65 天 7.2 小时 14.4 分钟
99.9%(三个九) 8.76 小时 43.8 分钟 1.44 分钟
99.95% 4.38 小时 21.9 分钟 43.2 秒
99.99%(四个九) 52.56 分钟 4.38 分钟 8.64 秒
99.999%(五个九) 5.26 分钟 26.3 秒 0.86 秒

四、Error Budget:把稳定性变成可消费的资源

Error Budget(错误预算)是 Google SRE 最重要的概念之一。它的核心思想是:如果 SLO 不是 100%,那么 SLO 与 100% 之间的差距就是你”允许犯错”的空间

计算方法

如果 SLO 是 99.9% 的可用性,每月的 Error Budget 是:

Error Budget = 1 - SLO = 1 - 99.9% = 0.1%
每月总请求数 = 1000 万
允许的失败请求数 = 1000 万 × 0.1% = 1 万

或者按时间计算:

每月总分钟数 = 30 × 24 × 60 = 43200 分钟
允许的不可用分钟数 = 43200 × 0.1% = 43.2 分钟

Error Budget 的消耗

Error Budget 像一个存款账户。每次故障都会从中”消费”一部分:

Error Budget 策略

当 Error Budget 还有剩余时——团队可以:

当 Error Budget 即将耗尽或已经耗尽时——团队必须:

这就是 Error Budget 的仲裁价值。产品经理说”我要上新功能”,工程师说”系统不稳定不能上”——这个争论没有赢家,因为双方都在用主观判断。但如果引入 Error Budget:

争论的对象从”人的判断”变成了”数据”,这是 SLO 工程最大的组织价值。

flowchart TD
    A["每月 Error Budget 重置"] --> B{"当前 Budget 剩余"}
    B -->|充足 > 50%| C["正常迭代<br/>功能发布、实验"]
    B -->|告警 20%-50%| D["谨慎迭代<br/>增加发布审批<br/>优先修复已知问题"]
    B -->|耗尽 < 20%| E["冻结功能发布<br/>全力修复可靠性<br/>事后复盘"]
    C --> F["监控 Budget 消耗速率"]
    D --> F
    E --> G["恢复 Budget 后解冻"]
    G --> F
    F --> B

五、多窗口燃烧率告警

传统的可用性告警方式是设定一个固定阈值:如果过去 5 分钟的错误率超过 1%,就触发告警。这种方式有两个严重问题:

  1. 告警太多(高噪声)。短暂的错误率波动(比如一次网络抖动导致 5 秒内错误率飙升到 5%,然后立刻恢复)会频繁触发告警,工程师被告警疲劳淹没。
  2. 告警太慢(低灵敏度)。低速率的持续故障(比如错误率持续在 0.5%,远低于 1% 的阈值)可能不会触发告警,但如果持续一整天,消耗的 Error Budget = 0.5% × 24 小时 / 720 小时 ≈ 1.67%,30 天下来就消耗了 50%。

Google SRE Workbook 提出的解决方案是多窗口燃烧率告警(Multi-Window, Multi-Burn-Rate Alerting)

燃烧率(Burn Rate)

燃烧率是 Error Budget 消耗速率的倍数。

燃烧率 = 实际错误率 / (1 - SLO)

如果 SLO = 99.9%(允许错误率 = 0.1%):

多窗口策略

单一时间窗口的燃烧率告警仍然有问题:短窗口(5 分钟)容易误报,长窗口(1 小时)反应慢。解决方案是同时检查两个窗口:

Google SRE Workbook 推荐的配置:

告警级别 长窗口 短窗口 燃烧率阈值 Budget 消耗占比(如果持续) 响应要求
紧急(Page) 1 小时 5 分钟 14.4x 2%(1 小时内) 立即响应
紧急(Page) 6 小时 30 分钟 6x 5%(6 小时内) 立即响应
工单(Ticket) 3 天 6 小时 1x 10%(3 天内) 工作时间处理

Prometheus 实现

以下是基于 Prometheus 的多窗口燃烧率告警规则示例:

# 预聚合:计算不同时间窗口的错误率
groups:
  - name: slo-error-rate
    rules:
      # 5 分钟窗口错误率
      - record: slo:http_request_error_rate:5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          /
          sum(rate(http_requests_total[5m]))

      # 30 分钟窗口错误率
      - record: slo:http_request_error_rate:30m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[30m]))
          /
          sum(rate(http_requests_total[30m]))

      # 1 小时窗口错误率
      - record: slo:http_request_error_rate:1h
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[1h]))
          /
          sum(rate(http_requests_total[1h]))

      # 6 小时窗口错误率
      - record: slo:http_request_error_rate:6h
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[6h]))
          /
          sum(rate(http_requests_total[6h]))

  - name: slo-burn-rate-alerts
    rules:
      # 紧急告警:14.4x 燃烧率,1h/5m 双窗口
      - alert: SLOBurnRateCritical
        expr: |
          slo:http_request_error_rate:1h > (14.4 * 0.001)
          and
          slo:http_request_error_rate:5m > (14.4 * 0.001)
        labels:
          severity: critical
        annotations:
          summary: "SLO burn rate critical: 14.4x"
          description: "Error budget will be exhausted in less than 2 hours."

      # 紧急告警:6x 燃烧率,6h/30m 双窗口
      - alert: SLOBurnRateHigh
        expr: |
          slo:http_request_error_rate:6h > (6 * 0.001)
          and
          slo:http_request_error_rate:30m > (6 * 0.001)
        labels:
          severity: warning
        annotations:
          summary: "SLO burn rate high: 6x"
          description: "Error budget will be exhausted in less than 5 days."

Grafana Dashboard 设计

SLO Dashboard 需要回答三个核心问题:

  1. 当前状态:现在 SLO 达标吗?用一个大号的指示灯(绿/黄/红)。
  2. Budget 剩余:这个周期还剩多少 Error Budget?用一个进度条。
  3. 消耗趋势:按当前速度,Budget 会在什么时候耗尽?用一条趋势线。
┌─────────────────────────────────────────────────┐
│  API 可用性 SLO: 99.9%                            │
│  ┌───────┐  ┌──────────────────────┐             │
│  │ 🟢    │  │ Budget: 72% 剩余     │             │
│  │ 达标   │  │ ████████████░░░░░    │             │
│  └───────┘  └──────────────────────┘             │
│                                                   │
│  当前可用性: 99.94%  |  月初至今 Error Budget 消耗: 28%│
│  ────────────────────────────────────────────     │
│  Budget 消耗趋势                                   │
│  100%|                                   ╱        │
│      |                               ╱            │
│      |                           ╱                │
│      |     当前消耗线 ────────╱                     │
│      |─────────────────                           │
│    0%|________________________________ 时间        │
│       1日      10日      20日     30日              │
└─────────────────────────────────────────────────┘

六、SLO 作为架构决策的仲裁者

SLO 不仅仅是告警的依据。在架构决策中,SLO 提供了一个客观的判断框架。

场景一:要不要引入缓存层

“缓存能提升性能”是一个模糊的理由。用 SLO 来分析:

但缓存引入了新的故障模式:缓存集群不可用时,所有请求穿透到数据库,可能导致数据库过载。需要评估这个故障模式对可用性 SLO 的影响。

场景二:要不要做服务拆分

一个单体服务的可用性是 99.95%。如果拆分成 3 个串行调用的微服务,假设每个微服务的可用性也是 99.95%:

串联系统可用性 = 99.95% × 99.95% × 99.95% = 99.85%

可用性从 99.95% 下降到 99.85%,月度停机时间从 21.9 分钟增加到 65.7 分钟。如果 SLO 是 99.9%,这个拆分方案直接导致 SLO 无法达标。

这不意味着不能拆分,但意味着拆分后必须通过其他手段(重试、熔断、降级)来补偿可用性损失。SLO 让这个权衡变得可量化。

场景三:数据库选型

在选择数据库时,不同产品的可靠性特征不同:

如果服务的 SLO 是 99.95% 可用性,选项 B 的 99.9% 可用性已经低于服务 SLO,仅数据库一个组件就可能耗尽全部 Error Budget。选项 A 虽然延迟稍高,但可用性更有保障。


七、SLO 驱动的组织行为

SLO 的影响不仅限于技术层面,它改变了组织的行为模式。

产品 vs. 工程的冲突仲裁

最常见的冲突是:产品团队想快速发布新功能,工程团队想花时间修复技术债务和提升稳定性。

没有 SLO 时,这个冲突的解决方式通常是:谁的职级高听谁的,或者谁声音大听谁的。结果是要么稳定性被牺牲(系统频繁出故障),要么迭代速度被牺牲(团队变得过度保守)。

有 SLO 和 Error Budget 后,规则变成:

这个机制的关键是双方在事前就同意了规则。Error Budget 策略应该以书面文件的形式存在,由产品负责人和工程负责人共同签字。

发布节奏的量化管理

SLO 和 Error Budget 还可以用来量化发布的”安全余量”。

假设每次发布平均消耗 2% 的月度 Error Budget(来自灰度发布期间的少量错误)。如果月度 Error Budget 总量是 43.2 分钟,那么每次发布平均消耗 0.864 分钟。一个月最多可以做 43.2 / 0.864 ≈ 50 次发布。

如果团队想增加发布频率(比如从每周发布改为每日发布),就需要降低每次发布消耗的 Error Budget——这意味着需要投资更好的灰度发布机制、更完善的自动化回滚、更细粒度的特性开关(Feature Flag)。

事后复盘的量化标准

什么事故值得做事后复盘(Postmortem)?一个实用的标准是:消耗了超过 N% 月度 Error Budget 的事故

例如:

这比传统的”影响了 X 个用户”或”持续了 X 分钟”的分类标准更有意义,因为 Error Budget 已经综合考虑了影响范围和持续时间。


八、工程案例:某电商平台的 SLO 落地实践

以下案例来自国内某中型电商平台的公开技术分享,展示了从”没有 SLO”到”SLO 驱动”的转变过程。

背景

第一步:选择 SLI(第 1-2 周)

团队识别出 3 个核心用户旅程(User Journey):

  1. 商品浏览:用户搜索商品 → 查看商品详情。
  2. 下单支付:加入购物车 → 提交订单 → 完成支付。
  3. 物流查询:查看订单状态 → 查看物流信息。

为每个旅程选择了 SLI:

用户旅程 SLI:可用性 SLI:延迟
商品浏览 搜索 API 成功率 搜索 API p99 延迟
下单支付 下单 API 成功率 下单 API p99 延迟
物流查询 物流 API 成功率 物流 API p99 延迟

度量点选择在 API 网关层(Nginx 访问日志),因为这是距离用户最近的可控度量点。

第二步:设定 SLO(第 3-4 周)

基于过去 90 天的历史数据:

SLI 历史实际水平 设定的 SLO
搜索 API 成功率 99.95% 99.9%
搜索 API p99 延迟 420 毫秒 500 毫秒
下单 API 成功率 99.92% 99.9%
下单 API p99 延迟 850 毫秒 1000 毫秒
物流 API 成功率 99.97% 99.9%
物流 API p99 延迟 200 毫秒 300 毫秒

SLO 设定略低于历史水平,给发布和实验留出 Error Budget。

第三步:建设可观测性(第 5-8 周)

第四步:制定 Error Budget 策略(第 9-10 周)

产品总监和技术总监共同签署了 Error Budget 策略文件:

  1. 每月 1 日重置 Error Budget。
  2. Budget > 50%:正常迭代,每周发布。
  3. Budget 20%-50%:增加发布前的 Staging 环境验证步骤,取消”紧急发布”通道。
  4. Budget < 20%:冻结功能发布,全力修复可靠性问题,直到 Budget 恢复到 30% 以上。
  5. 大促期间(双十一前后两周):冻结所有非关键变更。

效果

运行 6 个月后的数据:


九、SLO 的常见误区

误区一:SLO 越高越好

99.99% 听起来比 99.9% “好”,但它意味着 10 倍的成本和 10 倍的运维压力。大多数业务不需要四个九。一个内部管理系统的 SLO 设为 99.99% 是资源浪费。

误区二:SLO = SLA

SLO 是内部目标,SLA 是外部承诺。它们的受众、后果和设定逻辑完全不同。把 SLO 和 SLA 设为同一个值,意味着你没有安全边际——SLO 刚被突破就要赔钱。

误区三:只关注可用性

很多团队只设了可用性 SLO,忽略了延迟 SLO。一个可用性 99.99% 但 p99 延迟 10 秒的系统,用户体验并不好。可用性和延迟应该成对出现。

误区四:SLO 定了就不改

SLO 应该是一个活文档(Living Document)。业务变化了(用户量翻倍)、架构变化了(从单体拆成微服务)、用户期望变化了——SLO 都需要相应调整。建议每季度 Review 一次 SLO 的合理性。

误区五:忽视依赖链

你的服务依赖数据库、缓存、消息队列、第三方 API。每个依赖都有自己的可靠性上限。如果你的数据库 SLA 是 99.95%,你的服务 SLO 就不可能比 99.95% 更高(除非你有完善的降级方案)。设定 SLO 时,必须考虑整个依赖链的可靠性。


十、SLO 工程的权衡

维度 没有 SLO SLO 过于宽松 SLO 合理 SLO 过于严格
稳定性管理 靠感觉 形同虚设 数据驱动 过度保守
产品迭代速度 不可预测 无约束 有节奏 接近停滞
告警噪声 高(随意设阈值) 低(几乎不告警) 低(有意义的告警) 极高(频繁告警)
工程师体验 疲于救火 缺乏挑战 专注 疲于奔命
故障复盘 凭感觉分级 缺乏标准 Budget 驱动的分级 过度复盘
成本 低(无额外投入) 中(可观测性投入) 高(冗余和容灾投入)
组织信任 中(团队压力大)
适用阶段 早期探索 不推荐 成长期和成熟期 极端关键系统

十一、结论

SLO 工程的核心不是设定一个数字,而是围绕这个数字建立一套决策机制:

  1. 选择正确的 SLI——度量用户真正关心的指标,在靠近用户的位置度量。
  2. 设定合理的 SLO——不太紧(杀死迭代),不太松(形同虚设),基于历史数据和业务需求。
  3. 实施 Error Budget 策略——把”稳定性 vs. 功能开发”从主观争论变成量化决策。
  4. 建设多窗口燃烧率告警——减少告警噪声,提高告警质量。
  5. 让 SLO 驱动架构决策——缓存要不要加、服务要不要拆、数据库怎么选——都可以用 SLO 来量化评估。

SLO 不是银弹。它不能阻止故障发生,也不能自动修复 Bug。但它提供了一个共同语言,让产品、工程、管理层在”可靠性应该是多少”这个问题上达成共识。在大多数团队中,这个共识的价值远大于任何单一技术改进。


导航

上一篇:容灾架构:多活与灾备设计

下一篇:容量规划:从拍脑袋到数据驱动


参考资料

书籍

论文与博客

工具

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-13 · architecture

【系统架构设计百科】告警策略:如何避免"狼来了"

大多数团队的告警系统都在制造噪声而不是传递信号。阈值告警看似直观,实则产生大量误报和漏报,值班工程师在凌晨三点被叫醒,却发现只是一次无害的毛刺。本文从告警疲劳的工业数据出发,拆解基于 SLO 的多窗口燃烧率告警算法,深入 Alertmanager 的路由、抑制与分组机制,结合 PagerDuty 的告警疲劳研究和真实工程案例,给出一套可落地的告警策略设计方法。

2026-04-13 · architecture

【系统架构设计百科】故障排查方法论:从告警到根因的系统化路径

凌晨三点的告警响了,你打开笔记本,盯着一堆指标不知道从哪里下手——两小时后发现是配置改错了。这种经历几乎每个 oncall 工程师都有过。本文从 Incident Command System 在 SRE 中的适配讲起,拆解从告警到根因的系统化排查路径,覆盖事件分级、假设驱动调试、事后复盘的无责文化、Google 与 Meta 的 oncall 体系,给出可落地的 Runbook 模板和 Postmortem 模板。

2026-04-13 · architecture

【系统架构设计百科】架构质量属性:不只是"高可用高性能"

需求评审时写下的'高可用、高性能、高并发',到了架构设计阶段几乎无法落地——因为它们不是可执行的需求。本文从 SEI/CMU 的质量属性理论出发,用 stimulus-response 场景模型把模糊需求变成可量化、可验证的架构约束,并拆解属性之间的冲突与联动关系。

2026-04-13 · architecture

【系统架构设计百科】复杂性管理:架构的核心战场

系统复杂性是架构腐化的根源——本文从 Brooks 的本质复杂性与偶然复杂性划分出发,结合认知负荷理论与 Parnas 的信息隐藏原则,系统阐述复杂性的来源、度量与控制手段,并给出可操作的架构策略


By .