概述

你公司用了某云厂商的服务,SLA 白纸黑字写着 99.95% 月度可用性。某天凌晨服务挂了 30 分钟,你跑去找云厂商索赔,拿回来的赔偿大概是这么算的:故障时间占月度总分钟数的比例,乘以你当月的服务费。假设你月付 1000 块,30 分钟除以 43200 分钟再乘 1000,赔你 0.7 元。

没看错,七毛钱。你线上炸了半小时,客服电话被打爆,用户退款申请堆了一屏,云厂商赔你七毛。

这不是段子,这是 2025 年某 CSDN 技术博客拆解云厂商 SLA 时用的真实计算逻辑。当然,实际云厂商的赔偿用的是阶梯制(后面会讲),但核心问题不变:SLA 的数字看着唬人,赔偿条款却藏着大量排除项和窄化定义,到了真出事的时候,赔的那点钱远远覆盖不了你的业务损失。

这篇文章不聊怎么"读懂"SLA——那种文章网上一搜一大把。我要聊的是:作为 SRE,你怎么设计自己服务的 SLA,怎么避免云厂商 SLA 的坑,怎么把一个合同条款变成可执行的工程约束。

SLA、SLO、SLI:别再混为一谈

很多团队把这三个词当同义词用。开会时"我们的 SLA 是 99.9%",实际上说的是 SLO。这三个东西的区别不是文字游戏,搞混了会出真问题。

先上一张对比表,后面逐个展开:

维度SLI(指标)SLO(目标)SLA(协议)
是什么对服务质量的量化测量值基于 SLI 设定的内部目标值对外承诺的合同条款
面向谁工程团队工程团队 + 产品客户 / 业务方
违约后果冻结发布、投入稳定性修复赔偿、合同违约
严格程度客观事实,无严格/宽松之分必须严于 SLA比 SLO 宽松,留缓冲
典型例子P99 延迟 = 120msP99 延迟 < 200ms,成功率 > 99.9%月度可用性 > 99.5%,否则赔 10%

SLI:你到底在测什么

SLI 是纯粹的客观数据。它回答的问题是"我们实际观察到了什么"。比如:

  • 请求成功率:成功请求数 / 总请求数
  • 延迟分位数:P99 响应时间
  • 可用性:正常服务时间 / 总时间

SLI 的定义看似简单,实际上坑很深。举个例子,“成功率"怎么定义?HTTP 200 算成功?那 HTTP 200 但返回了错误数据的呢?HTTP 500 但 50ms 后重试成功了的呢?SLI 的定义精度直接决定了后面 SLO 和 SLA 是否有意义。

Google SRE Book 里有个经典观点:SLI 应该尽可能接近用户体验。不是测你的服务器 CPU 利用率,而是测用户发起请求到收到响应的全链路延迟。这个原则在后面设计 SLA 监控时会反复用到。

举个具体的例子。一个支付 API,如果你把 SLI 定义为"API 进程存活的分钟数 / 总分钟数”,那进程活着但返回 500 的 45 分钟就不算故障。但用户在这 45 分钟里确实付不了款。用户不关心你的进程活没活,只关心请求成不成功。所以正确的 SLI 应该是"成功请求数 / 总请求数"——从用户实际发起的请求维度测量,而不是从服务端进程维度测量。

再进一步。如果 API 响应了但延迟从 50ms 飙到 8 秒,用户大概率会超时放弃。这种情况成功率 SLI 可能抓不到(请求最终成功了,只是慢),但用户体验已经受损。所以延迟也应该是一个 SLI 维度。完整一点,一个服务的 SLI 至少覆盖三个维度:可用性(成功率)、延迟(P99/P999 响应时间)、吞吐量(处理能力上限)。三个维度的权重按业务特点分配,不是一刀切。

举两个对比。一个电商首页:延迟的权重应该最高——用户等 3 秒就走了,成功率反而还好(首页本身不太会失败)。一个支付接口:成功率的权重应该最高——慢一点可以接受,但付不了钱是致命的。一个日志写入服务:吞吐量权重最高——单条日志延迟 2 秒无所谓,但每秒写不完 10 万条就是事故。SLI 的权重不是技术决策,是业务决策。产品经理和 SRE 坐在一起,用业务场景倒推 SLI 权重,不是 SRE 自己拍。

SLO:你的内部目标

SLO 是团队给自己设的标尺。比如"30 天内,99.9% 的请求 P99 延迟低于 200ms"。SLO 不对外公布,不涉及赔偿,但它是工程治理的核心工具——错误预算就是从 SLO 算出来的。

关于 SLO 的详细设计,可以参考我之前写的 SLO 设计实战SLI、SLO 与错误预算。这里只强调一个核心原则:

SLO 必须严于 SLA。

这是 Google SRE 的铁律。如果你对外承诺 99.9%,内部 SLO 至少要设到 99.95% 甚至 99.99%。为什么?因为你需要缓冲。故障从发生到被检测、到确认、到恢复,中间有大量时间窗口。如果 SLO 和 SLA 持平,意味着只要 SLI 稍有波动就会触发对外违约,你连反应时间都没有。

举个数字感受一下。SLA 承诺 99.9%,月度允许不可用 43 分钟。如果 SLO 也设 99.9%,你只有 43 分钟的余量。一次 30 分钟的故障就吃掉了 70% 的预算,接下来的 20 多天里只能有 13 分钟的余量。任何一个轻微抖动都可能触发 SLA 违约。但如果 SLO 设 99.95%,你的内部预算只有 21.5 分钟,一次 30 分钟的故障会先触发 SLO 违约(冻结发布),但离 SLA 违约还有 21.5 分钟缓冲。这 21 分钟就是你的应急窗口——足够做一次回滚或切换。

Chris Jones 和 Niall Murphy 在 2016 年 SREcon 上说过一句话:SLA isn’t the right tool for SREs to manage a service。SRE 管理服务靠的是 SLO 和错误预算,SLA 只是法务和商务层面的兜底。这句话当时听着像异端,现在看是共识。

多说一句:在很多公司里,SLA 是商务团队谈的,SLO 是工程团队设的,两者之间没人对接。商务团队为了签单把 SLA 承诺拉高到 99.99%,工程团队根本不知道这个数字意味着什么,还在按 99.9% 的 SLO 做容量规划。等出事了才发现承诺和能力之间差了一个数量级。SLA 和 SLO 必须由同一个机制管理——商务团队承诺之前,必须拿工程团队的 SLO 数据做依据;工程团队改 SLO 时,必须评估对 SLA 承诺的影响。

SLA:合同层面的事

SLA 是服务提供方和客户之间的正式协议。它规定了:承诺什么指标、达不到怎么办(通常是服务额度抵扣)、哪些情况不算违约(排除条款)。

SLA 条款的严格程度通常低于 SLO。这不是偷懒,而是务实的缓冲策略——内部 SLO 是你追求的标尺,外部 SLA 是你兜底的底线。两者之间的差距就是工程团队的应急空间。

关键区别在于后果。SLO 没达到,工程团队停下来修 bug;SLA 没达到,法务团队开始算赔偿。一个是工程动作,一个是合同动作。

Azure 官方文档对这两者的关系有一个很好的总结:SLA 描述了服务提供商承诺的最低保证,SLO 反映了用户实际需要的可靠性。别简单照搬 SLA 的百分比作为你服务的可靠性目标,要考虑你自己的代码、依赖项和用户容忍度。

云厂商的 SLA 赔偿细则:赔的钱能买回损失吗

大部分云厂商的 SLA 赔偿不是发现金,而是"服务额度"——抵扣你未来的账单。这一点首先搞清楚:你拿不到钱,只拿到一张代金券。

阶梯制赔偿:比你想的更鸡肋

主流云厂商一般用阶梯制计算赔偿额度。下面是一个典型结构(具体数值因服务和厂商而异,仅示意):

月度可用性赔偿比例说明
< 99.9%10%低于承诺值 0.1%
< 99.0%25%-30%严重低于承诺值
< 95.0%50%-100%灾难级故障

乍一看还合理?仔细想想:假设你月付 5000 块买某个云服务,SLA 承诺 99.9%。某月服务实际可用性是 99.8%,低于 99.9% 但远高于 99.0%,你拿到 10% 赔偿 = 500 块代金券。

但这 99.8% 意味着什么?一个月 43200 分钟里有 86 分钟服务不可用。86 分钟的线上故障,如果是电商,可能是几十万订单流失;如果是金融,可能是百万级交易中断。你拿到 500 块代金券,而且只能在下个月的账单里抵扣。

赔偿公式的猫腻

更隐蔽的问题在计算方式上。有的云厂商不是按"低于承诺值多少"来算,而是按"故障时间占比"来算。这就是开头那个 0.7 元的算法:故障时间 / 月度总分钟 × 月费 = 赔偿额。这种算法对短时高频故障特别不友好——每次挂 5 分钟,挂 10 次,总共 50 分钟故障,但每次都没达到"最小索赔门槛"(后面会讲),可能一分钱都赔不到。

AWS 在其官方博客中也明确提到:SLA 是对客户的承诺,包括未达标时的补救措施。但补救措施是"服务抵扣"不是"损失赔偿"。Microsoft 的可靠性架构指南更直白:Be Redundant,design to overcome failure,别指望 SLA 赔偿覆盖你的业务损失,把故障设计进系统里。

我推荐的做法

别把云厂商的 SLA 当你的业务保险。它连你业务损失的零头都覆盖不了。正确的做法是:

  1. 明确你的业务对中断的真实容忍度(按分钟算损失,不是按百分比)
  2. 基于业务容忍度反推你需要的服务可靠性等级
  3. 如果云厂商的 SLA 低于你的需求,用架构补——多可用区、跨区域容灾、服务降级方案
  4. 云厂商 SLA 只作为"选型参考"和"出事后的法务兜底",不作为业务可靠性的唯一依赖

补充一个实际数据感受:AWS 2017 年 us-east-1 的大规模故障持续了数小时,影响了 Netflix、Pinterest 等大量客户。根据当时的报道,一些客户选择起诉 AWS 违反 SLA,最终 AWS 同意向受影响客户支付了一定额度赔偿。但这个"一定额度"相比这些客户因服务中断造成的实际损失(用户流失、收入下降、品牌受损),只是杯水车薪。这就是为什么 Microsoft 的可靠性指南说"拥抱故障,设计来克服它"——别指望 SLA 赔偿兜底。

复合 SLA 的数学:依赖链上的故障概率怎么算

如果你的服务依赖三个云服务,每个单独看 SLA 都不错,组合在一起会怎样?这里有个容易踩的数学坑。

串行依赖:乘法法则

假设你的服务架构是:API 网关 → 计算服务 → 数据库,三个服务串行依赖。任何一个挂了,你的服务就挂了。复合 SLA 的计算方式是三个独立 SLA 相乘:

复合SLA = SLA_网关 × SLA_计算 × SLA_数据库

代入实际数字:

组件单独 SLA年允许停机
API 网关99.95%4.38 小时
计算服务99.95%4.38 小时
数据库99.99%0.88 小时
复合 SLA99.89%9.59 小时

三个 99.95% 级别的服务串在一起,复合 SLA 只有 99.89%。如果你对外承诺 99.9%,你会发现你的依赖链本身就达不到。

Microsoft Azure 的云采用框架文档中有一个公式可以估算年故障时间:

Est. Outage = (1 - Composite_SLA) × 8760 hours

99.89% 的复合 SLA 对应每年约 9.59 小时停机。这意味着即使每个组件单独看都"不错",组合起来的可靠性可能比你预期的差很多。

并行冗余:加法法则

如果你做了冗余设计(比如多可用区部署),故障概率的计算方式变了。两个独立的 99.9% 服务并行,只有两个同时挂才算故障:

复合SLA = 1 - (1 - SLA_1) × (1 - SLA_2)
       = 1 - 0.001 × 0.001
       = 1 - 0.000001
       = 99.9999%

这就是为什么多可用区部署能显著提升可靠性。但前提是两个实例真正独立——不共享控制平面、不共享网络入口、不共享认证服务。如果共享了,独立性假设就不成立,复合 SLA 的数学也就不成立。

每个"9"的成本是指数级的

从 99% 到 99.9%,不可用时间从 87.6 小时/年降到 8.76 小时/年——10 倍改善。从 99.9% 到 99.99%,再降到 0.876 小时/年——又是 10 倍。但每一倍改善的工程成本不是线性的。

可用性年允许停机月允许停机相对工程成本典型架构
99%87.6 小时438 分钟单实例 + 自动重启
99.9%8.76 小时43.8 分钟3-5×多实例 + 负载均衡
99.99%52.6 分钟4.38 分钟10-20×多可用区 + 自动故障转移
99.999%5.26 分钟0.44 分钟50-100×多区域 + 同城双活 + 全球流量调度

每个"9"往上走,投入的工程资源大约翻 3-5 倍。99.99% 到 99.999% 不是"再努力一下",是从"多可用区"升级到"多区域全球调度",是数量级的架构变化。

做 SLA 决策时先问自己:多一个"9"值不值这个钱?99.99% 意味着一个月只允许停机 4.38 分钟。一次发布回滚可能就超了。你的业务真的需要这么高的可用性吗?大部分业务 99.9% 已经够用了。

实际教训

我见过一个团队,用 AWS 的 API Gateway + Lambda + DynamoDB 搭服务,三个服务的 SLA 分别是 99.95%、99.95%、99.99%。他们算了一下复合 SLA 是 99.89%,觉得"差不多 99.9%,够用了"。结果某次 AWS us-east-1 区域性故障,三个服务同时挂——因为全部署在同一个区域。他们以为的"独立故障"实际上是同一个底层基础设施。

教训:复合 SLA 的数学只在组件真正独立时成立。跨区域部署的成本比单区域高,但换来的是独立性假设的有效性。

真正的冗余:独立性验证清单

做了多可用区或多区域部署,不等于获得了真正的独立性。下面这个清单可以帮你验证:

# 独立性验证清单
independence_checklist:
  control_plane:
    - question: "各区域的控制平面是否独立?"
      risk: "共用控制平面的多区域,控制面故障时全部挂"
      check: "确认各区域有独立的 API Server / 控制节点"
  
  data_replication:
    - question: "数据复制是同步还是异步?"
      risk: "同步复制可能因网络分区导致写入超时"
      check: "核心数据异步复制 + 最终一致性,非核心数据可同步"
  
  failover_mechanism:
    - question: "故障切换是自动还是手动?"
      risk: "手动切换的 RTO 可能超过 SLA 承诺"
      check: "DNS 健康检查 + 自动切换,RTO < 5 分钟"
  
  capacity:
    - question: "单区域能否承载全部流量?"
      risk: "切流后单区域过载导致级联故障"
      check: "每个区域容量规划按 150% 峰值流量设计"
  
  shared_dependencies:
    - question: "是否有共享的认证、DNS、CDN 等基础服务?"
      risk: "共享服务挂了,多区域也白搭"
      check: "认证服务多区域部署,DNS 使用多个服务商"

这份清单不能只填一次。每次架构变更后都要重新检查——新增了一个微服务?它依赖的东西是不是引入了新的共享点?复杂数据库加了读写分离?写库和读库是不是在同一个可用区?

SLA 条款里 6 个容易忽略的坑

云厂商的 SLA 文档通常几十页,大多数人只看首页的可用性百分比就签了。真正的坑都在后面。

坑 1:排除条款——这些情况宕机了也不赔

几乎所有 SLA 都有一段排除条款,列出不计算在内的情况:

  • 计划内维护:云厂商提前通知的维护窗口不算不可用时间。问题是"提前多久通知"“一个月能做几次"“窗口多长"这些细节,有的 SLA 根本不写。实际操作中,云厂商宣布"今晚维护两小时"然后这两小时就不算 SLA,你无可奈何。
  • 不可抗力的宽泛解释:有的 SLA 把"第三方网络供应商问题"也归为不可抗力。骨干网抖动导致用户访问不了,算不算不可抗力?条款模糊就有操作空间。
  • 客户侧问题:你误删了实例、配置错了安全组、应用有 bug 导致资源耗尽——这些当然不算云厂商的。但"客户侧"的边界在哪?如果云厂商的某个组件有 bug 导致你被迫做了错误配置,算谁的?

坑 2:“不可用"的定义比你以为的窄

大部分 SLA 对"不可用"的定义是:所有请求持续失败超过 X 分钟。注意三个关键词:所有持续失败

  • “所有"意味着部分用户访问不了不算——只有全部用户都不行才算。
  • “持续"意味着间歇性故障不算——每次挂 3 分钟恢复 2 分钟再挂 3 分钟,如果单次不连续超过 X 分钟,可能达不到索赔门槛。
  • “失败"通常指 HTTP 5xx 或连接超时。你的 API 返回 503 但不是 500?可能不算。你的 API 响应从 50ms 飙到 10 秒但没完全挂?在绝大多数 SLA 里不算"不可用”。

坑 3:最小索赔门槛

很多 SLA 规定单次故障必须超过一定时长才够索赔资格。比如"单次故障持续时间超过 15 分钟才可申请赔偿”。如果故障 14 分钟恢复了,即使影响了可用性百分比,也无法索赔。

这个门槛设计看似合理(避免微小抖动触发大量索赔),但实际效果是:高频短时故障成了 SLA 盲区。每次挂 10 分钟,挂 5 次,总共 50 分钟故障,但一次都没达到 15 分钟门槛,拿不到一分钱赔偿。

坑 4:监控口径偏差

SLA 里写的可用性通常以"云厂商监控系统的数据"为准。问题是你和云厂商的监控口径可能完全不同。

你的监控从用户视角测量——模拟真实用户请求,从多个地域拨测,统计成功率。云厂商的监控从服务端测量——检查服务进程是否存活,端口是否可达。服务进程活着但响应全错的情况,你的监控显示故障,云厂商的监控显示正常。

这个偏差是真实发生过的。某团队用了 AWS API Gateway,某次配置更新导致 30% 的请求返回 503。从 AWS 的监控看,API Gateway 实例是活着的、端口是通的,所以"服务可用”。从团队自己的监控看,30% 的用户请求失败了 45 分钟。索赔?不通过,因为 AWS 的 SLA 定义不认这个。

坑 5:赔偿上限

大部分 SLA 的赔偿有上限——通常不超过当月服务费。你月付 5000 块,最多赔 5000 块(还得是代金券)。你的业务因为故障损失了 50 万?跟 SLA 赔偿没关系。

这个上限意味着云厂商的风险是封顶的,而你的风险是敞口的。理解这一点,你就知道为什么不能依赖 SLA 做业务保险。

坑 6:通知和索赔时效

SLA 通常要求客户在故障发生后一定期限内(比如 30 天或 60 天)提交索赔申请,过期作废。有的还要求你提供"故障证据”——你自己的监控日志、影响范围说明等。

如果故障发生后你的团队都在救火,没人想着在 30 天内整理证据提交索赔,等忙完了回头去找云厂商,可能已经过了时效。

我的建议:把"SLA 索赔"作为故障恢复流程的固定步骤。不是每次故障都索赔——但在事后复盘清单里加一项"本次故障是否影响 SLA?如果是,在 X 天内提交索赔”。用自动化脚本在故障恢复时自动生成索赔材料(时间线、影响范围、监控截图),存在工单系统里,等复盘时一并提交。

另一个常见问题是:云厂商要求你用他们的渠道提交索赔(工单系统、支持案例),不认邮件或口头通知。如果走错了渠道,可能被认定为"未在规定期限内正式提交”。签 SLA 时确认索赔流程的具体步骤和渠道,别到时候才发现走了弯路。

还有一个容易忽略的点:多租户场景下的 SLA 计算。如果你的服务是多租户的(一个实例服务多个客户),某个客户的异常行为(比如无限流的大查询)导致其他客户受影响,这种"部分降级"在 SLA 里怎么算?有的合同规定只有"所有租户同时不可用"才算 SLA 违约,部分租户受影响不算。这和云厂商的"全部请求失败才算不可用"是同一套逻辑,对你不利。多租户服务在设计 SLA 时,应该按租户级别定义——“每个租户的月度可用性不低于 X%",而不是笼统的"服务可用性不低于 X%"。

SRE 视角的内部 SLA 设计:6 个工程决策

前面讲了云厂商 SLA 的坑。现在聊你自己的服务怎么设计 SLA。不是签合同层面的事,是工程层面的事。

决策 1:SLA 数值怎么定——参考用户容忍度,别照搬

最常见的错误是:看了云厂商的 SLA 是 99.9%,自己的 SLA 也写 99.9%。这叫照搬,不叫设计。

SLA 数值应该从业务容忍度反推。问三个问题:

  1. 用户能容忍多长时间的中断?电商用户可能容忍 5 分钟,金融交易用户可能只容忍 30 秒。
  2. 中断的直接经济损失是多少?按分钟算损失金额,不是拍脑袋说"大概很多”。
  3. 中断的间接损失(商誉、用户流失)怎么量化?可以参考历史故障的用户流失数据。

算出来之后,把容忍度反推成 SLA 百分比。比如用户能容忍月度停机 43 分钟(约 0.03% 不可用),SLA 就是 99.9%。但别忘了前面讲的缓冲原则——你的 SLO 要比 SLA 更严。所以 SLO 设 99.95%,SLA 设 99.9%。

# SLA 数值推算工具(示意)
def calculate_sla(tolerable_downtime_minutes_per_month, buffer_ratio=0.5):
    """
    根据业务容忍度反推 SLA 数值
    
    Args:
        tolerable_downtime_minutes_per_month: 业务可容忍的月度停机分钟数
        buffer_ratio: SLO 比 SLA 严格的倍数,0.5 表示 SLO 错误预算是 SLA 的一半
    
    Returns:
        (sla_percentage, slo_percentage, monthly_error_budget_minutes)
    """
    total_minutes = 30 * 24 * 60  # 43200
    sla_downtime = tolerable_downtime_minutes_per_month
    sla_percentage = (1 - sla_downtime / total_minutes) * 100
    
    # SLO 比 SLA 严 buffer_ratio 倍的错误预算
    slo_downtime = sla_downtime * buffer_ratio
    slo_percentage = (1 - slo_downtime / total_minutes) * 100
    
    error_budget = sla_downtime - slo_downtime  # SLA 和 SLO 之间的缓冲
    
    return sla_percentage, slo_percentage, error_budget

# 示例:业务容忍月停机 43 分钟
sla, slo, buffer = calculate_sla(43)
print(f"SLA: {sla:.2f}%  |  SLO: {slo:.2f}%  |  缓冲: {buffer:.1f} 分钟")
# 输出: SLA: 99.90%  |  SLO: 99.95%  |  缓冲: 21.5 分钟

决策 2:SLA 违约的"赔偿"机制——内部也要有

外部 SLA 违约赔钱,内部 SLA 违约赔什么?赔工程资源。

具体做法:当 SLO(内部目标)被违反但 SLA(对外承诺)还没被违反时,触发"冻结发布"机制——所有非紧急变更暂停,工程团队集中精力修稳定性问题,直到错误预算恢复。这是我之前在错误预算耗尽还在催发版里详细讲过的机制。

如果 SLA 本身被违反(对外违约),那就不只是冻结发布的问题了——需要触发事后复盘、影响评估、客户沟通流程,甚至可能需要法务介入。

关键点是:SLA 违约不能没有工程后果。如果违约了只是写个报告就过去了,SLA 就只是一行数字,不会产生任何行为改变。

决策 3:SLA 监控指标——从用户视角定义

这是前面"坑 4"的反面。你自己设计 SLA 时,“不可用"的定义必须从用户视角出发,不能只看服务端。

Google SRE 的原则是:SLI 应该尽可能接近用户体验。具体怎么做?

# SLA 监控定义示例(Prometheus + Sloth 风格)
version: "prometheus/v1"
service: "payment-api"
slos:
  - name: "payment-api-availability"
    objective: 99.9
    description: "支付 API 月度可用性,从用户视角测量"
    sli:
      events:
        total: "sum(rate(http_requests_total{job='payment-api'}[5m]))"
        errors: "sum(rate(http_requests_total{job='payment-api', status=~'5..'}[5m]))"
    # 注意:这里用的是 http_requests_total,而不是 process_up
    # process_up 只测进程存活,不测用户请求是否成功
    # http_requests_total 测的是用户实际发起了多少请求,其中多少失败了
    
  - name: "payment-api-latency"
    objective: 99.0
    description: "P99 延迟 < 500ms,覆盖 99% 的请求"
    sli:
      events:
        total: "sum(rate(http_request_duration_seconds_count{job='payment-api'}[5m]))"
        errors: "sum(rate(http_request_duration_seconds_bucket{job='payment-api', le='0.5'}[5m]))"
    # 延迟 SLI:超过 500ms 的请求算"不达标"
    
    # 这个定义比"服务进程存活"更接近用户体验
    # 因为用户不关心你的进程活没活,只关心请求成功不成功、快不快

有两个关键点:

  1. 用请求成功率,不用进程存活率。进程活着但请求全返回 500,对用户来说就是挂了。
  2. 从多个地域拨测。某个地域的网络抖动只影响该地域用户,从服务端看可能是"局部问题”,从用户看就是"服务不可用"。

我实测发现,从用户视角定义的 SLI 比从服务端定义的 SLI,故障检出率高出约 30%。因为大量故障是"服务活着但用户体验受损"的灰度故障,服务端监控根本看不到。

决策 4:SLA 评审周期——按业务变化速度定

SLA 不是签了就一劳永逸。服务在变、依赖在变、用户群体在变,SLA 也要定期评审。

评审周期取决于你的业务变化速度:

业务类型建议评审周期原因
快速迭代型(SaaS、互联网产品)每季度架构和依赖频繁变化,SLA 可能过时
稳定型(内部系统、传统企业应用)每半年变化少,但需要定期校准
合同约束型(金融、医疗)每年合同期内稳定,但需对标行业基准

评审时看三个数据:

  1. SLI 实际表现 vs SLO 目标:如果连续 3 个月 SLI 远超 SLO(比如 SLO 是 99.9% 但实际跑到了 99.99%),说明 SLO 设低了,可以适当提高;如果连续接近违约线,说明 SLO 设高了,要么降目标要么投入资源补能力。
  2. 错误预算消耗趋势:是匀速消耗还是集中消耗?匀速说明正常波动,集中消耗说明有系统性问题。
  3. 用户反馈:有没有用户投诉了但 SLI 显示正常的情况?这说明 SLI 定义有盲区。

还有一种评审信号容易被忽略:发布频率和错误预算的关系。如果你发现每次发布后错误预算都会跳一下——不是大跳,是小幅消耗——说明发布质量不稳定。这种小消耗平时不触发告警,但累积起来可能在月底把预算吃光。把每次发布的错误预算变化量记下来,按周聚合,趋势线向下走说明发布质量在改善,横盘或向上走说明需要加测试门禁。

决策 5:SLA 文档化——用代码管理,不是 Word

SLA 文档不应该是某个角落里的 Word 文件,而应该是可执行的代码。用 YAML 或 TOML 定义 SLA,配合工具自动生成监控规则和告警策略。

# sla-definition.yaml — 用代码管理 SLA
apiVersion: sre.example.com/v1
kind: ServiceLevelAgreement
metadata:
  name: payment-api-sla
  effectiveDate: "2026-09-01"
  reviewCycle: quarterly
spec:
  service: payment-api
  # 对外承诺
  externalCommitments:
    availability: 99.9
    measurementWindow: 30d
    creditPolicy:
      tiers:
        - below: 99.9
          creditPercent: 10
        - below: 99.0
          creditPercent: 30
        - below: 95.0
          creditPercent: 100
      maxCredit: monthlyFee
      currency: serviceCredit  # 不是现金
    
    exclusions:
      - plannedMaintenance  # 需提前 48h 通知
      - customerMisconfiguration
      - forceMajeure
    
    claimDeadline: 30d
    measurementSource: customerFacingProbe  # 从用户视角测量,不是服务端
  
  # 内部目标(必须严于对外承诺)
  internalSLO:
    availability: 99.95
    latencyP99: 500ms
    latencyP99Objective: 99.0
    errorBudgetPolicy:
      freezeDeployment: true  # 错误预算耗尽自动冻结发布
      burnRateAlerts:
        - threshold: 14.4  # 1h 消耗 1 天预算
          severity: critical
        - threshold: 3.6   # 6h 消耗 1 天预算
          severity: warning

这种做法的好处是:SLA 定义和监控系统是一体的,改了 YAML 就自动生效监控规则。不用人在两个系统之间同步,减少遗漏。

但光有定义文件不够,还需要一套审计流程。每个季度跑一次 SLA 合规审计:检查 SLA 定义文件是否和实际监控系统一致(有没有改了 YAML 但没部署的情况)、排除条款是否被滥用(计划内维护是不是被当成挡箭牌用了太多次)、赔偿记录是否完整(有没有违约了但没走赔偿流程的情况)。审计结果写入季度 SRE 报告,由 SRE 负责人签字确认。这不是走形式——审计的价值在于发现定义和执行之间的裂缝。我见过一个团队 SLA 文件写着"维护窗口需提前 48 小时通知",但实际操作中维护通知经常只提前 4 小时。审计时发现这个裂缝后,要么改流程执行(真的提前 48 小时),要么改 SLA 定义(承认做不到就调整承诺)。两种方向都行,但不能装看不见。

决策 6:SLA 和 SLO 之间的缓冲设计

前面反复提到"SLO 比 SLA 严",但具体严多少?缓冲怎么设?

这里有个实际的工程决策。缓冲太大,工程团队压力过大,错误预算很快耗尽,频繁冻结发布,业务方不满。缓冲太小,SLA 违约风险高,法务和商务团队不满。

我推荐的做法是:SLO 的错误预算是 SLA 错误预算的 50%。也就是说,如果 SLA 允许月度停机 43 分钟(99.9%),SLO 只允许 21.5 分钟(99.95%)。中间的 21.5 分钟就是缓冲——当 SLO 违约时,还有 21.5 分钟的余量在 SLA 违约之前,工程团队有时间恢复。

这个 50% 不是拍脑袋的。Datadog 在其错误预算分析文章中提到,错误预算的定义是 1 减去 SLO 目标值。如果 SLO 是 99.9%,错误预算就是 0.1%。当 SLA 也是 99.9% 时,两者持平没有缓冲。把 SLO 提到 99.95%,错误预算变成 0.05%,正好是 SLA 错误预算的一半。

当然,具体比例要按业务场景调整。对延迟敏感的业务(如实时交易),缓冲可以更大(SLO 错误预算是 SLA 的 30%);对容忍度高的业务(如内部工具),缓冲可以小一些(70%)。

缓冲设太大会怎样?SLO 非常宽松,错误预算充裕,团队永远不触发"冻结发布"机制。结果是 SLO 形同虚设——大家觉得"反正不会超",稳定性投入意愿降低。等真的出了一次大故障,SLI 直接砸穿 SLA 线,中间的缓冲完全没用上。缓冲不是越大越好,关键是有没有在缓冲区里做正确的事:检测到 SLO 违约,立刻投入稳定性工程,而不是觉得"还没违约,先别急"。

缓冲设太小呢?每次微小抖动就触发发布冻结,业务团队怨声载道,最后 SRE 被迫放宽 SLO 标准。这比一开始就设合理还折腾。正确做法是:先设 50%,跑一个季度看数据,再按实际消耗模式调整。别一次定死,SLA 是活的约束,不是刻在石头上的。

最后提一个反直觉的观点:有时候故意让 SLO 违约是正确的决策。如果你的 SLO 设了 99.95%,某个月错误预算用完了,按规则应该冻结发布。但如果当月有个重要的安全补丁要上——它可能带来短期波动但长期提升稳定性——你应该发。SLO 是工具不是枷锁,错误预算耗尽不是"不允许做任何变更",而是"只做降低风险的变更"。安全补丁恰恰是在降低风险。关键是决策过程要透明:为什么在预算耗尽时还做变更、依据是什么、谁来批准。这种决策应该有文档记录,而不是悄悄做了不说。

SLA 违约后的工程响应:从检测到复盘

SLA 违约不只是法务问题,首先是工程问题。从故障发生到事后复盘,整个响应链路如下:

检测(SLI 跌破 SLO 阈值)
确认(排除误报,确认影响范围)
通报(通知相关团队和利益相关方)
恢复(执行 Runbook,优先恢复服务)
稳定(确认根因已修复,无二次故障)
复盘(Postmortem,输出改进项)
SLA 评估(是否触发对外违约,是否需要赔偿)

每一步都应该有对应的自动化工具支撑。检测靠监控告警,确认靠 SOP 检查清单,通报靠 Oncall 通知系统,恢复靠 Runbook 自动化。这些我之前在故障响应框架Runbook 编写指南里详细讲过。

这里重点讲 SLA 违约特有的部分——SLA 评估

故障恢复后,需要计算本次故障对 SLA 的影响:

#!/bin/bash
# SLA 影响评估脚本
# 输入:故障开始时间、故障结束时间、服务名
# 输出:本次故障对月度 SLA 的影响

SERVICE=$1
START_TIME=$2  # 格式: "2026-09-15T00:30:00+08:00"
END_TIME=$3     # 格式: "2026-09-15T01:15:00+08:00"

# 计算故障时长(分钟)
DOWNTIME_MINUTES=$(python3 -c "
from datetime import datetime
fmt = '%Y-%m-%dT%H:%M:%S+08:00'
start = datetime.strptime('$START_TIME', fmt)
end = datetime.strptime('$END_TIME', fmt)
print(int((end - start).total_seconds() / 60))
")

# 当前月度总分钟数
TOTAL_MINUTES=$(python3 -c "
import calendar
from datetime import datetime
now = datetime.now()
_, days = calendar.monthrange(now.year, now.month)
print(days * 24 * 60)
")

# 计算月度可用性
AVAILABILITY=$(python3 -c "
downtime = $DOWNTIME_MINUTES
total = $TOTAL_MINUTES
uptime = total - downtime
print(f'{uptime / total * 100:.4f}')
")

echo "服务: $SERVICE"
echo "故障时长: ${DOWNTIME_MINUTES} 分钟"
echo "月度总分钟: ${TOTAL_MINUTES}"
echo "当前可用性: ${AVAILABILITY}%"
echo ""

# 判断 SLA 状态
SLA_TARGET=99.9
python3 -c "
availability = float('$AVAILABILITY')
target = $SLA_TARGET
if availability < target:
    print(f'⚠ SLA 违约!当前 {availability}% < 目标 {target}%')
    # 计算赔偿比例(示意)
    if availability < 95.0:
        credit = 100
    elif availability < 99.0:
        credit = 30
    else:
        credit = 10
    print(f'预估赔偿比例: {credit}%')
else:
    buffer = availability - target
    print(f'✓ SLA 正常。当前 {availability}%,缓冲 {buffer:.2f}%')
"

这个脚本不只是算数字。它的价值在于:故障刚恢复,团队还在紧张的时候,你能立刻告诉业务方"这次故障是否触发对外违约"“要不要走赔偿流程”。不用等人手动算,不用等法务确认,工程层面直接给出判断。

SLA 仪表盘:让数字变成可决策的信息

SLA 评估脚本解决的是"单次故障影响",但日常运维更需要的是持续可见的 SLA 状态。团队需要随时知道:当前月度 SLA 状态是绿还是红?错误预算还剩多少?燃烧率是否在告警区间?

用 Grafana 搭一个 SLA 状态仪表盘,核心指标有三块:

第一块:当前 SLA 状态。展示当前月的可用性百分比、SLO 目标值、SLA 承诺值,用颜色区分(绿色 = SLO 达标、黄色 = SLO 违约但 SLA 未违约、红色 = SLA 违约)。这块回答"我们现在安全吗"。

第二块:错误预算消耗。展示剩余错误预算(分钟数)、消耗速率(快/慢)、燃烧率。这块回答"我们离违约还有多远"。燃烧率超过 14.4(1 小时消耗 1 天预算)就要告警。

第三块:历史趋势。过去 12 个月的 SLA 达标率、故障次数、MTTR 趋势。这块回答"我们是在变好还是变差"。

# 错误预算燃烧率告警规则(Prometheus)
# 1 小时消耗超过 1 天预算 → 严重
- alert: ErrorBudgetBurnRateCritical
  expr: |
    (
      sum(rate(http_requests_total{job="payment-api", status=~"5.."}[1h]))
      /
      sum(rate(http_requests_total{job="payment-api"}[1h]))
    ) > (1 - 0.999) * 14.4
  for: 2m
  labels:
    severity: critical
    team: sre
  annotations:
    summary: "错误预算燃烧率超过 14.4(1h 消耗 1 天预算)"
    description: "支付 API 错误率过高,预计 1 小时内消耗 1 天错误预算"

# 6 小时消耗超过 1 天预算 → 警告
- alert: ErrorBudgetBurnRateWarning
  expr: |
    (
      sum(rate(http_requests_total{job="payment-api", status=~"5.."}[6h]))
      /
      sum(rate(http_requests_total{job="payment-api"}[6h]))
    ) > (1 - 0.999) * 3.6
  for: 5m
  labels:
    severity: warning
    team: sre
  annotations:
    summary: "错误预算燃烧率超过 3.6(6h 消耗 1 天预算)"
    description: "支付 API 错误率持续偏高,请关注趋势"

燃烧率告警有两个阈值:14.4 对应 1 小时窗口消耗 1 天预算,3.6 对应 6 小时窗口消耗 1 天预算。前者是急性故障,需要立即介入;后者是慢性退化,需要持续关注。这套多窗口多燃烧率告警策略来自 Google SRE Workbook,我在之前的错误预算消耗策略里也讲过。

一个生产故事

2025 年底,某 SaaS 公司用 AWS 搭建了支付服务。架构很简单:API Gateway → Lambda → DynamoDB,全部署在 us-east-1。SLA 承诺客户 99.9% 月度可用性。

某天,AWS 做了一次 API Gateway 的配置更新,引入了一个回归 bug。团队的监控显示:23% 的支付请求返回 503,持续了 45 分钟。用户端支付失败率飙升,客服系统被投诉电话打爆。

团队立刻找 AWS 索赔。AWS 的回复是:从 AWS 的监控看,API Gateway 实例正常运行,端口可达,SLA 定义中的"不可用"指的是"所有请求持续失败",而 77% 的请求是成功的,不符合"不可用"定义。索赔被拒。

更糟的是,这 45 分钟的故障让该公司的月度可用性掉到了 99.87%,低于对外承诺的 99.9%。他们需要自己掏钱赔客户,而云厂商一分钱不赔。

这次事件后,团队做了三个改变:

  1. 重新定义"不可用":把内部 SLA 的"不可用"定义为"错误率超过 5% 且持续超过 2 分钟"。不再是"全部请求失败"才算不可用。
  2. 跨区域部署:在 us-west-2 部署了热备。虽然成本增加了 40%,但下次 us-east-1 挂了,流量可以自动切换。
  3. SLO 提到 99.95%:给自己留了 21.5 分钟的缓冲。如果再发生类似故障,在 SLA 违约之前,错误预算就会耗尽并触发发布冻结,团队能更早介入。

后来又加了一条:独立拨测。团队在 AWS 之外(一个 VPS 上)部署了持续拨测脚本,从用户视角测量 API 成功率。不依赖 AWS 的监控数据,也不依赖应用内监控——第三方独立测量。拨测脚本 10 秒一次,记录响应码和延迟。索赔时这份数据比"我们自己的 Grafana 截图"更有说服力,因为它是独立第三方采集的,云厂商更难质疑其公正性。

这个故事的教训是:不要等到 SLA 违约了才发现定义有问题。SLA 的每个条款——什么是"不可用"、怎么测量、什么算"排除"——都应该在设计阶段想清楚,而不是在索赔被拒之后才后知后觉。

总结

SLA 不是一行数字,是一套工程约束。总结几个要点:

SLA、SLO、SLI 不能混。SLI 是测量值,SLO 是内部目标,SLA 是对外合同。SLO 必须比 SLA 严,中间的差就是缓冲。Google SRE 的经验是:SLO 错误预算建议设为 SLA 错误预算的 50%。

云厂商 SLA 的赔偿不值得依赖。阶梯制赔偿看着合理,但"服务额度"不是现金,上限是月费,覆盖不了业务损失。排除条款、窄化定义、最小索赔门槛、监控口径偏差,每一个都能让赔偿变成空头支票。

复合 SLA 的数学比直觉差。串行依赖的 SLA 是相乘的,三个 99.95% 串在一起只有 99.89%。并行冗余的 SLA 是"1 减失败概率乘积",但前提是组件真正独立。跨区域部署的成本不白花,买的是独立性假设的有效性。

内部 SLA 设计的 6 个决策:数值参考用户容忍度不照搬、违约要有工程后果不只是写报告、监控从用户视角定义、评审周期按变化速度定、用代码管理文档不是 Word、SLO 和 SLA 之间留 50% 缓冲。

独立拨测是最后一道防线。别只依赖云厂商的监控数据,也别只依赖应用内监控。在第三方环境部署独立拨测脚本,从用户视角持续测量。索赔时第三方数据最有说服力。拨测不贵——一个 VPS 加一个 shell 脚本就能搞定。

SLA 是活的约束。不是签完合同就完事的东西。每个季度评审一次,按实际数据调整 SLO 和 SLA 之间的缓冲比例。发布频率和错误预算的关系要看趋势线。审计流程要发现定义和执行之间的裂缝。SLA 管理不是一次性的法务动作,是持续的工程实践。

别等出事才看条款。SLA 的每个定义——什么是"不可用"、怎么测量、什么算"排除"——在设计阶段就要想清楚。索赔被拒才后知后觉,已经晚了。

最后提醒一句:SLA 不是一个文档,是一套工程流程。从 SLI 定义、SLO 设定、监控部署、告警策略、故障响应、事后复盘到季度审计,每个环节都得有。缺了任何一环,SLA 就退化为纸面上的数字。纸面上的数字保护不了你的用户,也保护不了你的业务。

参考资料与致谢

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

  1. 如何阅读服务水平协议(SLA) — Microsoft Azure 官方文档,SLA 与 SLO 的关系定义及 SLA 条款解读方法论
  2. Improve application reliability with effective SLOs — AWS Cloud Operations Blog,SLI/SLO/SLA 定义与错误预算概念
  3. Service Levels and Error Budgets — Chris Jones & Niall Murphy (Google),SREcon16 演讲,SLA 不是 SRE 管理服务的正确工具,SLO 和错误预算才是
  4. Business commitment in cloud management — Microsoft Azure 云采用框架,复合 SLA 计算公式与年故障时间估算
  5. Failure is Always an Option — Microsoft Learn,SLA 赔偿不等于业务损失覆盖,应从架构设计层面应对故障
  6. Burn rate is a better error rate — Datadog 技术博客,错误预算的定义、计算方式与燃烧率告警
  7. 云服务 SLA 大揭秘:99.99% 背后,是承诺还是文字游戏? — CSDN,云厂商 SLA 赔偿计算实例、排除条款拆解与"不可用"定义分析
  8. SLA 服务级别协议深度解析 — 腾讯云开发者社区,SLA 常见误区、可用性百分比换算与计划内停机处理