SLI、SLO、SLA怎么区分?错误预算与压测落地指南
半夜两点被电话叫醒打开监控大盘发现核心接口成功率掉到了98.7%值班同学说“还没到告警线啊”业务方在群里说“用户下不了单了”。这种场面我经历过不止一次而每次吵到最后问题都会绕回同一个原点SLI、SLO、SLA这三个词团队里每个人的理解都不一样。有人把可用性99.9%挂在嘴上却说不清是怎么算出来的有人把SLO当成对外承诺写进合同还有人压测跑出一堆漂亮数字上线后一周就被真实流量打回原形。这篇东西想干的事很直接用大白话把这三个缩写的分工掰开揉碎配几个我在实际项目里遇到的案例再单独拿一章讲压测——因为绝大多数SLO定得不靠谱根子都在压测阶段就已经跑偏了。不管你是刚接手稳定性工作的开发、被要求“定个SLO”的运维还是要在合同里写可用性条款的技术负责人看完至少能避开我踩过的那几个坑。1. 三个缩写到底谁管谁从一次线上故障复盘会说起1.1 一场吵了两小时的复盘会三种理解互相打架先说个真实发生过的场景。某公司的核心下单接口出了故障持续了四十多分钟事后复盘会上三方各执一词。运维同学拿出监控说“这段时间接口成功率是99.2%我们内部目标定的是99.5%确实没达标但也不算灾难。”业务负责人立刻反问“99.2%是什么口径按请求数算还是按分钟算我这边看到的是有七分钟完全不可用。”而法务那边更直接“合同里写的是月度可用性不低于99.9%现在这个月还剩二十天你们打算怎么补”你看同一件事三个人说的是三个不同的东西。运维说的是实测值业务说的是用户体验的颗粒度法务说的是白纸黑字的承诺。这三者恰好对应SLI、SLO、SLA但因为平时混着用一到关键时刻就变成鸡同鸭讲。我后来在团队里推这套体系时第一件事不是建监控、不是写文档而是把这三个词的定义贴在群里要求所有人用统一的说法。听起来很形式主义但效果立竿见影——因为一旦大家用同一套词汇争论的焦点就会从“你到底在说什么”转移到“这个阈值合不合理”后者才是真正有价值的问题。1.2 用体温计、健康目标和体检报告来理解三者关系如果非要找个生活化的类比我一般这么讲SLI是体温计上的读数。它就是一个客观测量值37.2度、38.5度不带任何判断。对应到系统里就是“过去5分钟的成功率是99.31%”“P99延迟是420毫秒”“每分钟处理订单数是3200笔”。注意SLI必须是可测量、可复现的同一段时间、同一个口径两个人算出来应该是同一个数。如果算不出来同一个数那它就不是合格的SLI。SLO是你给自己定的健康目标。比如“体温不超过37.3度”“这个季度体重控制在65公斤以内”。它带判断但判断标准是你自己定的可以商量可以调整。系统里就是“过去28天滚动窗口内下单接口成功率不低于99.9%”。SLO是内部目标用来指导工程决策的——它决定了你什么时候该停下来修稳定性、什么时候可以继续堆功能。SLA是写进体检报告、要交给保险公司的那份承诺。它带后果。达不到就要赔钱、给信用额度、或者触发合同里的补救条款。所以SLA一定是对外的一定有违约成本也一定比SLO更宽松——因为没人会傻到把自己内部都经常达不到的数字写进合同。这三者的关系可以用一句话概括SLI是原料SLO是内部加工标准SLA是对外卖出去的产品规格。原料质量决定你能不能达标加工标准决定你内部怎么管产品规格决定你违约要赔多少。三者之间如果出现“SLA比SLO还严格”这种倒挂那基本等于自己给自己挖坑。1.3 为什么很多团队定了SLO却完全没用起来我见过不少团队SLO文档写得漂漂亮亮贴在Confluence上一年没人点开。原因通常有三个。第一个是SLO和实际决策脱钩。SLO定完就放那儿发布照发、需求照排没有任何一个流程会因为“错误预算快用完了”而踩刹车。这种SLO就是个装饰品。第二个是SLI的采集口径和SLO的定义对不上。SLO写的是“成功率”但监控里的成功率把健康检查请求也算进去了把CDN回源的4xx也当成失败了数字天然偏高永远达不到告警线。这种属于地基没打牢。第三个是SLO一刀切。整个系统就一个99.9%核心支付链路和后台导出报表用同一个标准。结果就是核心链路天天红边缘链路天天绿时间长了大家对告警彻底麻木。后面几章我会逐个拆开讲SLI怎么选才靠谱、SLO怎么定才不拍脑袋、SLA怎么写才不给自己挖坑最后单独一章讲压测——因为很多SLO本身就是压测阶段定歪的。2. SLI你究竟拿什么来量这个系统的体温2.1 四个黄金信号但不是四个都要用业界常提的“四个黄金信号”是延迟、流量、错误、饱和度。这个框架很好用但新手最容易犯的错是四个全上每个都建一堆图表最后 dashboard 上几十个面板真出事的时候没人看得过来。我的建议是分主次。对绝大多数面向用户的在线服务错误率成功率和延迟是主指标流量和饱和度是辅助指标。为什么因为用户感知最直接的就是“能不能用”和“快不快”。成功率掉了用户立刻报错延迟涨了用户立刻觉得卡。而饱和度和流量更多是用来做提前预警和根因定位的它们告诉你“为什么成功率掉了”而不是直接告诉用户系统坏了。举个具体的例子。某内容平台的核心是“内容详情页加载”他们的SLI设计大概是这样的SLI名称计算口径用途详情页请求成功率非5xx响应数 / 总请求数主指标用户直接感知详情页P99延迟服务端处理耗时的99分位主指标用户直接感知数据库连接池使用率活跃连接 / 最大连接辅助指标根因定位单机QPS每秒请求数辅助指标容量规划注意这里的细节成功率只算非5xx不算4xx。为什么因为4xx很多时候是用户自己的问题参数错、没登录把它算进失败率会让指标失真而且会诱导团队去“优化”那些本来就不该成功的请求。但这也带来一个坑——如果某次发布把大量请求变成了400成功率看起来是100%实际上业务已经挂了。所以4xx要单独有个监控曲线只是不进主SLI。2.2 平均延迟是所有指标里最容易骗人的一个这一点我踩过很深的坑值得单独说。早年做某活动页的时候压测报告上写着“平均响应时间85毫秒非常优秀”上线后用户投诉卡顿。回头拉P99一看2.3秒。原因是那批请求里有大量静态资源走缓存毫秒级返回把平均值拉得很低而真正涉及数据库查询的那部分请求慢得像蜗牛。平均值这个统计量对长尾分布天然不敏感。所以延迟类SLI必须用分位数而且要用两个分位P99看最差的那1%P50看中位数体感。有些场景还要看P999比如金融交易、支付回调这种对少数慢请求极其敏感的链路。P99和P50之间的差距本身就是个很好的健康度信号——如果P50是80毫秒、P99是3秒说明系统内部有明显的抖动源可能是GC、可能是锁竞争、可能是某个下游偶发超时。还有一个容易被忽略的点延迟的口径要明确是服务端耗时还是端到端耗时。服务端耗时只算到服务返回响应为止端到端还要加上网络传输、客户端渲染。这两者可能差出几百毫秒。SLO里写“P99延迟200毫秒”如果不写清楚是哪个口径等于没写。2.3 采集口径不一致是SLI体系崩掉的头号原因我再强调一遍这个坑因为它太常见了。某个团队监控系统算出来的成功率是99.95%业务侧从网关日志算出来是99.6%差了0.35个百分点。开会吵了半天最后发现是监控系统把健康检查探针的请求也算进去了——探针每分钟打一次永远是200相当于给分母里塞了一堆必然成功的样本。请求量小的时候这点探针能把成功率拉高好几个点。解决这类问题的办法只有一个把SLI的计算逻辑写成代码放进版本管理所有系统引用同一份实现。具体做法可以是写一个统一的指标上报SDK或者在监控平台里把查询语句固化成模板禁止各团队各写各的。我们当时的做法是把PromQL查询固化到告警规则库里每个SLI对应一条固定的查询任何人要改都得走代码评审。给个具体示例成功率的标准查询大概是这样sum(rate(http_requests_total{joborder-api, status!~5..}[5m])) / sum(rate(http_requests_total{joborder-api}[5m]))延迟的P99大概是这样基于Histogramhistogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket{joborder-api}[5m])) by (le) )注意这里的两个细节一是status!~5..而不是status~2..因为3xx重定向通常也算成功二是rate的窗口用[5m]而不是[1m]窗口太短数字抖动大太长了又反应慢。5分钟是个比较折中的选择具体可以按业务量调整。提示SLI的查询语句一定要先在测试环境用历史数据回放验证一遍确认数字和你手工抽样统计的结果对得上再上生产。这一步花半小时能省掉后面几个月的扯皮。3. SLO阈值定在哪里才不算是拍脑袋3.1 从用户体感倒推而不是从服务器容量倒推这是我见过的最普遍的错误先看服务器能扛多少QPS然后倒推一个SLO写上去。这种做法的问题在于它回答的是“系统能跑多快”而不是“用户能忍受多慢”。正确的方向应该反过来。先问用户在这个功能上能忍受多大概率失败能忍受多长的等待我一般会做几件事。第一是拉真实数据看历史上用户在这条链路上的行为——比如支付页面的超时时间用户平均等多久会关掉页面重试。第二是找业务方聊问清楚“哪类失败最伤用户”。第三是看竞品和行业惯例虽然不能照抄但能有个参照。举个具体的例子。某电商的订单提交接口一开始定的SLO是“成功率99.9%P99延迟500毫秒”。后来做用户调研发现用户在点击“提交订单”之后等待超过1.5秒就会开始重复点击导致重复下单。也就是说延迟对用户的实际影响在1.5秒这个点上有个明显的拐点。于是他们把SLO改成“P99延迟800毫秒P999延迟1.4秒”把优化资源集中在压住那1%的长尾上反而比笼统地追“平均500毫秒”更有效。这个例子说明一件事SLO的数值应该来自对用户行为拐点的观察而不是拍脑袋的整数。99.9%和99.95%看起来只差0.05个点换算成错误预算是从每月43分钟降到21分钟差一倍。这0.05个点要不要去追取决于用户能不能感知到以及追它的工程成本有多大。3.2 错误预算SLO里最有价值、也最被浪费的部分错误预算的概念很简单SLO是99.9%那错误预算就是0.1%。一个月按43200分钟算允许你坏43分钟。这个数字的价值不在于允许你坏而在于它给了团队一个可以量化的决策依据。我见过的有效用法大概有这么几类。第一类是发布闸门错误预算消耗超过50%时非紧急需求暂停发布优先修稳定性超过80%时只有修复类变更可以上线。第二类是容量决策如果连续三个窗口错误预算都在20%以下说明系统余量充足可以考虑降配省成本反之则要扩容。第三类是优先级排序两个技术债摆在一起先修那个能明显减少错误预算消耗的。但绝大多数团队的错误预算是“算了但没用”。原因往往是两个。第一个是没有分层的预算消耗速率告警。光看“本月已消耗X%”太迟钝了等发现的时候已经月底了。正确做法是多窗口多燃烧率告警——比如“1小时内消耗了当月预算的2%”这种能在故障初期就抓住。第二个原因更根本错误预算没有和任何流程绑定。消耗光了也没人管那它当然没人看。给个多窗口告警的思路用两个窗口交叉验证减少误报快窗口1小时燃烧率超过14.4倍即1小时烧掉2%的月度预算立即告警慢窗口6小时燃烧率超过6倍工单提醒第二天处理这两个阈值的推导其实不复杂一个月按30天算2%的预算如果均匀消耗对应的是14.4小时压到1小时烧完就是14.4倍速率。6小时烧掉5%对应的是6倍速率。数字可以根据自己团队对误报的容忍度调但思路必须是“短窗口抓急性故障、长窗口抓慢性劣化”。3.3 SLO必须分层一个标准管全系统等于没管这是我在多个项目里反复验证过的一条经验同一个系统内不同链路的SLO必须拉开差距。拿一个典型的内容社区产品举例大致可以分成三档链路层级典型功能建议SLO理由核心交易下单、支付、退款99.95%直接关联收入失败即损失核心浏览首页、详情页、搜索99.9%高频访问影响留存辅助功能评论、收藏、消息通知99.5%失败可容忍可异步补偿后台任务报表导出、数据同步99.0%无人实时感知可重试为什么要分层因为工程资源永远有限。如果所有链路都定99.95%团队会被无数的告警淹没最后变成“所有告警都不看”。分层之后核心链路的预算一旦消耗全团队都会紧张边缘链路偶尔抖一下走异步补偿就行不用惊动所有人。这才是可持续的稳定性管理。分层还有个附带好处它能逼着团队去梳理依赖关系。当你给某个接口定SLO的时候必然会问“它依赖哪些下游”下游的SLO能不能支撑上游的目标。这个梳理过程本身往往能发现一堆隐藏的单点。4. SLA写进合同的那句话背后全是坑4.1 SLA不是SLO的对外版本两者的责任主体不一样很多人以为SLA就是“把SLO换个说法写进合同”这个理解会出大问题。SLO是技术团队对自己的承诺责任主体是研发和运维衡量的是系统表现。SLA是商务层面的承诺责任主体是整个组织衡量的是客户能不能得到约定的服务达不到要赔钱。这两者的差别体现在三个地方。第一SLA一定比SLO宽松。内部目标99.95%对外承诺写99.9%中间这0.05个点是你的缓冲垫用来应对统计口径差异、计划内维护、以及客户侧网络问题带来的争议。如果两个数字一样或者SLA比SLO还严那等于没有任何缓冲任何风吹草动都要赔钱。第二SLA要写清楚排除条款。什么叫“不可用”计划内维护算不算客户自己的网络问题算不算上游第三方服务故障算不算这些如果不提前写清楚出事之后就是无休止的扯皮。我见过的比较稳妥的写法是把“不可用”定义得尽量窄比如“服务端连续5分钟返回5xx错误且该错误非客户侧原因导致”同时明确列出免责场景。第三SLA要有明确的补偿机制和上限。赔多少、怎么赔、上限是多少这些必须量化。常见的做法是按不可用时长折算服务费抵扣同时设一个总上限比如当月服务费的30%。没有上限的SLA条款对双方都是风险。4.2 统计口径的细节决定了SLA是护身符还是定时炸弹这一节讲的东西看起来琐碎但每一条都真实引发过争议。时间颗粒度。可用性是按分钟算、按小时算、还是按月算差异巨大。假设一个月内发生了60次每次10秒的抖动按月算可能只影响了几分钟可用性看起来还是99.99%按分钟算这60分钟全算不可用可用性立刻掉到99.86%。对外承诺时颗粒度越粗对你越有利但要和客户协商一致不能单方面定。请求量加权还是时间加权。同样一次故障发生在流量高峰期和凌晨三点按请求量加权算出来的影响完全不同。多数SLA采用时间加权因为好算、好举证但对客户来说未必公平。这个要在合同里写明白。多副本、多可用区怎么算。如果服务部署在三个可用区挂了一个但另外两个正常整体可用性怎么算是按“有任何一个副本可用就算可用”还是按“所有副本都不可用才算不可用”前者对服务方有利但要确保故障时的自动切换真的生效否则客户实际感受到的是完全不可用你却在按“理论可用”算账。谁来做测量。服务方自测还是客户侧探活两者结果经常不一致。比较稳妥的做法是约定一个第三方的探活点或者在合同里明确“以服务方监控数据为准客户如有异议需提供可复现的证据”。这句话看起来强势但至少把争议的解决路径定下来了。注意SLA里的每一个数字都要能在监控系统里找到对应的、口径一致的查询。如果合同写了99.9%但内部根本算不出这个数那这份SLA就是给自己埋的雷。4.3 内外差值怎么设计才既有缓冲又不显得心虚我一般的做法是内部SLO和对外SLA之间留0.5到1个数量级的缓冲。具体来说核心链路内部SLO定99.95%对外SLA写99.9%辅助链路内部99.5%对外不承诺或者只承诺99%。这个缓冲不是用来“瞒着客户”的它有三个正当用途。一是吸收统计口径差异——你内部按请求数算客户可能按分钟算结果天然不同。二是覆盖计划内维护窗口——如果维护不算违约那这部分时间要从SLA的分子分母里剔除。三是应对上游依赖的偶发问题——如果你的服务依赖某个第三方而合同里没把第三方故障列为免责那这个缓冲就是你的安全垫。反过来如果某个链路的内部SLO长期贴着SLA的红线跑那就说明这个SLA承诺过头了要么加投入把SLO提上去要么找商务重新谈条款。硬扛的结局一定是某个月集中爆雷。5. 压测实战SLO不是测出来的是验出来的5.1 压测目标要从SLO反推而不是从能压多高出发很多人做压测的思路是找台压测机把QPS一路往上加加到系统扛不住为止记下那个数字完事。这种做法能得到一个“极限容量”但对定SLO几乎没帮助因为它没有回答“在SLO要求的延迟下能扛多少量”。正确的做法是先明确目标。假设核心接口的SLO是“P99延迟800毫秒、成功率99.95%”那压测要验证的就是在P99不超过800毫秒且成功率不低于99.95%的前提下系统能承载的最大QPS是多少。这两个约束条件是硬边界一旦突破再高的QPS也没意义——因为那时候系统已经在违反SLO了。具体操作上我一般用阶梯加压配合断言。以wrk为例先跑一个固定并发观察延迟分布wrk -t8 -c200 -d300s --latency \ -s ./scripts/order_submit.lua \ http://target-host/api/order/submit跑完之后重点看输出的延迟分布表而不是只看平均值。同时并行采集服务端的P99和错误率如果发现P99开始抬头而QPS还没到预期说明瓶颈已经出现了这时候继续加压只会得到一堆违反SLO的数据。更稳妥的方式是用支持动态调整的压测工具把“P99超过阈值就停止加压”写成断言让压测自动停在SLO边界上。5.2 压测环境失真是压测结论不可信的头号原因这一节我列几个最典型的失真来源基本都是我亲身踩过的。数据量不对。测试库只有几万条数据生产库有几千万条索引的B树层数都不一样查询耗时能差一个数量级。压测前一定要用生产的脱敏数据或者至少用同等量级和分布的数据。缓存命中率失真。压测脚本往往是循环打同一批ID缓存全命中看起来快得飞起。生产环境的访问是长尾分布的大量冷数据要走磁盘。解决办法是在压测脚本里按真实访问分布采样请求参数或者直接在压测前清空缓存跑一轮冷启动。压测机自己成了瓶颈。见过太多次压测机CPU打满、网络带宽跑满被测系统其实还很闲但压测报告上显示“系统在3000QPS时延迟飙升”。判断方法很简单压测过程中看一眼压测机的负载如果它自己都快挂了那这轮数据直接作废。发压端最好用多台机器分布式压别指望一台搞定。连接池和线程池没有预热。很多系统在启动后的前几分钟性能很差因为连接池还在建立、JIT还没编译热点代码。如果压测直接从冷启动开始跑得到的数字会严重偏低。正确做法是先跑5分钟预热带再开始正式采集。单接口压测代替链路压测。单接口跑得飞快一上链路就崩因为中间还有服务间调用、序列化、限流、鉴权这些环节。核心链路的压测必须走完整的调用链哪怕会慢很多。5.3 压测数据怎么转成SLO调整的依据压测跑完拿到一堆数字接下来才是真正考验判断力的时候。假设压测结论是在P99不超过800毫秒的前提下系统稳定承载1.2万QPS而当前峰值流量是4000 QPS。这意味着有3倍余量看起来可以放心。但我一般还会追问几个问题。这个1.2万是在什么数据量和什么缓存状态下跑出来的如果是热缓存那真实余量要打折扣。这个数字的稳定性如何连续跑三轮每轮的结果波动有多大如果第一轮1.2万、第二轮9000、第三轮7000那说明系统有明显的不稳定因素实际可用容量要按最低的那轮算。流量继续增长的话先到瓶颈的是哪个组件是数据库连接数、是某个下游接口、还是带宽这个信息决定了下一轮扩容的方向。我遇到过的情况是压测跑出来余量很足但上线两周后SLO就频繁告警。回头查发现是流量结构变了——压测时按接口均分流量实际生产里某个重查询接口占了80%的流量压测根本没压到点上。所以压测的请求分布一定要贴近真实最好直接从生产网关日志里采样请求比例。还有一个反直觉的结论压测结果出来之后经常需要反过来调整SLO而不是调整系统。比如压测发现某功能的P99天然就在1.2秒左右追到800毫秒需要重构整个存储层成本极高。这时候如果用户对1.2秒并不敏感那更合理的做法是把SLO放宽到1.5秒把工程资源投到别处。SLO是服务于业务的不是拿来跟系统较劲的。6. 落地顺序和几个反复踩到的坑6.1 从零开始推这套体系我建议的推进顺序如果团队现在什么都没有别一上来就定SLO。我建议按这个顺序走。第一步先统一SLI的采集。挑一到两个最核心的接口把成功率、P99延迟的查询语句固化下来确保所有相关方看到的数字是同一个。这一步大概要花一两周主要时间花在对齐口径上。做完之后你会发现很多之前“感觉有问题”的地方其实数字是正常的只是大家看的口径不一样。第二步用历史数据反推一个初始SLO。别拍脑袋直接拉过去90天的数据看这条链路的实际表现分布。如果过去90天里P99的中位数是600毫秒、最大是1.1秒那SLO定在900毫秒左右比较合理——既有挑战性又不至于天天告警。初始SLO定得宽松一点没关系跑一两个窗口之后再收紧。第三步把错误预算和流程挂钩。哪怕只是最简单的一条规则预算消耗超过70%时周会上必须讨论。有了这条规则SLO才算真正开始起作用。第四步再考虑SLA。SLA涉及商务和法务周期长而且一旦签了就不容易改。在内部SLO还没跑稳定之前对外承诺要格外保守。6.2 几个让我印象最深的踩坑记录坑一告警阈值直接等于SLO阈值。SLO是99.9%告警就设在99.9%结果就是正常运行状态下告警天天响——因为99.9%本身就是个需要努力才能维持的目标日常波动到99.88%太正常了。正确的做法是告警设在SLO之下留出缓冲比如99.85%才告警同时用燃烧率做辅助判断。坑二把所有4xx都算成失败。这个前面提过但值得再说一次。某次大促前有人把成功率SLI改成“只有2xx算成功”结果发现成功率只有92%全组通宵排查最后发现是大量用户的token过期返回401导致的。这类“失败”不是系统故障是正常的鉴权行为。坑三压测数据没清理污染了生产。这个教训极其深刻。压测时往生产库写了大量测试订单导致后续的报表统计全部失真花了两天才清理干净。后来我们的规矩是涉及写操作的压测一律走影子库或者带流量标记绝不允许裸压生产。坑四SLO窗口选得太短。一开始用7天滚动窗口结果每次发版都会让SLO掉一截团队为了保住SLO不敢发版。后来改成28天滚动窗口单次发版的影响被摊薄反而更合理。窗口的选择要匹配发版节奏发版频繁的团队用长窗口更合适。坑五只盯着核心链路忽略了依赖。核心接口的SLO是99.95%但它依赖的一个配置服务SLO只有99%那核心接口的实际上限就是99%。这个道理很简单但实际梳理依赖关系的时候总会漏掉几个“看起来不重要”的组件。我现在的习惯是每季度做一次依赖拓扑的复查把那些SLO明显低于上游要求的依赖单独标出来。这几个坑里我觉得最值得警惕的是最后一个。因为前几个坑踩一次就长记性了而依赖关系是会随着系统演进不断变化的今天梳理清楚了明天加个新服务又可能出现新的短板。稳定性这件事本质上是个持续维护的活儿没有什么一劳永逸的方案。我自己现在的做法是把它塞进季度规划里每次做容量评估的时候顺带过一遍花不了多少时间但能避免很多“明明核心链路做得很好却还是出事”的困惑。