ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SLI、SLO、SLA区别与落地:从指标口径到压测验证

SLI、SLO、SLA区别与落地:从指标口径到压测验证

SLI、SLO、SLA 这三个词,我在面试里问过不下五十个人,能把三者关系说明白的不超过五个。更多人能背出全称——服务水平指标、服务水平目标、服务水平协议——但一到实际场景就露馅:问他“你们系统的可用性是多少”,回答“99.99%”;再问“这个数怎么算出来的、窗口多长、分母是什么、超了会怎样”,就开始支支吾吾。这三个词不是背概念用的,它们分别对应“怎么量”“量到什么程度算合格”“不合格赔多少钱”三件完全不同的事,混在一起谈,团队就会陷入一种很典型的内耗:运维说自己达标了,产品说用户还在骂,老板说合同要赔钱了,三方用的是三套口径。

我把这几年在几个项目里踩过的坑、写过的文档、压测现场翻过的车,整理成这一篇。内容覆盖 SLI 怎么挑、SLO 怎么定数字、SLA 怎么签条款,以及最容易被跳过但最要命的一环——压测实战里怎么把 SLI 真打出来、把 SLO 真验证掉。适合刚接手稳定性工作的同学,也适合已经在写 SLO 文档但总觉得落地不了的人。全文用大白话讲,配可抄的模板和脚本,不玩概念。

1. 先把三个词摆到桌面上:它们到底各管什么

1.1 一家餐厅的比喻,一次讲清三者的位置

我习惯用一家连锁餐厅来解释这三个概念,比任何官方定义都直观。

SLI 是服务员手里的记录本:这一单从点单到上菜用了多少分钟、这一单有没有上错菜、这一单客人有没有投诉。它是观测到的事实,是数字,不带任何感情色彩。记录本上写“今天 100 单里有 96 单在 30 分钟内上齐”,这就叫 SLI。

SLO 是店长给自己定的内部规矩:“我们要求 95% 的订单 30 分钟内上齐”。注意,这条规矩是给自己看的,是团队内部的努力方向。它比现状略高一点,够得着但不轻松,用来驱动改进。店长每天早上看记录本,发现昨天只有 93%,就知道后厨出问题了,得去查。

SLA 是跟外卖平台签的那份合同:“月度准时率低于 90%,每单赔 5 元”。它有外部对象、有赔付条款、有法律或商务后果。合同一旦签了,就不是“努力方向”了,是“必须守住的红线”。

三者串起来是一条因果链:用 SLI 观测现实,用 SLO 管理期望,用 SLA 约束商业风险。缺了 SLI,SLO 就是拍脑袋;缺了 SLO,SLA 就是裸奔;缺了 SLA,SLO 就没有商业抓手,做着做着就成了运维自娱自乐的报表。

这里有个特别容易被搞反的地方,我早年就栽过:对外承诺的 SLA,必须比内部追求的 SLO 更宽松,中间要留缓冲。当时我们内部 SLO 定 99.9%,转头跟客户签 SLA 也写 99.9%,觉得“一致才显专业”。结果那个月一次计划内变更抖了三分钟,内部刚好踩线达标,对外却已经违约,商务那边追着我们出说明。后来才明白,SLO 是“我要做到的水平”,SLA 是“我承诺不低于的水平”,前者必须留出余量兜住意外,顺序不能倒。

1.2 一张表把三者的差异钉死

概念讲多了容易糊,直接上对照表。这张表我一般会放在团队文档的第一页,新人进来先看这个。

维度SLI(指标)SLO(目标)SLA(协议)
本质一个可测量的数字给这个数字设的合格线带后果的对外承诺
面向对象工程团队自己工程团队 + 产品客户、合作方、商务
典型形式“成功请求占比 99.93%”“30 天滚动窗口内不低于 99.9%”“月度可用性低于 99.5% 按比例返还”
违反后果无,只是事实冻结发布、复盘、投入资源赔付、信用受损、合同风险
谁来定研发/运维研发 + 产品 + 业务商务 + 法务 + 技术
变更频率随架构变,几个月一次季度评审一年或按合同周期
严格程度事实最严(内部追求)最松(对外保底)

看最后一行,这就是最容易搞反的地方。很多团队把 SLA 写得比 SLO 还狠,等于自己给自己挖坑——内部还没跑稳就对外承诺高标准,后面所有精力都花在“解释为什么没达标”上,而不是“怎么变好”。

1.3 三个高频混淆点,逐个拆掉

混淆一:把资源指标当 SLI。CPU 使用率 85%、内存占用 70%、磁盘 IO 很高——这些是资源指标,不是 SLI。它们能解释“为什么慢”,但不能回答“用户有没有受影响”。一台机器 CPU 打满但请求照样 20ms 返回,用户根本无感;反过来 CPU 只有 30%,但数据库锁等待把 P99 拖到 2 秒,用户已经在骂了。SLI 必须站在用户视角,从请求入口统计,资源指标是排查时的辅助线索。

混淆二:把 SLO 当成一个孤零零的百分数。“我们 SLO 是 99.9%”这句话信息量为零。完整的 SLO 是四件套:指标定义 + 阈值 + 统计窗口 + 统计口径。同样是 99.9%,“按自然月、按请求数、剔除健康检查、剔除 4xx 客户端错误”和“按滚动 7 天、按分钟采样、不剔除任何流量”,算出来的结果能差出两个数量级。只报数字不报口径,等于没说。

混淆三:把 SLA 当技术文档。SLA 是要给商务和法务看的,里面每一条都会被拿去当证据。写“系统应保证高可用”这种话,等于没写,因为“高可用”无法度量;写“可用性不低于 99.9%”,就必须同时写清“可用性怎么算、数据谁提供、争议怎么处理”,否则真出事时双方各拿一份数据,谁也说服不了谁。

2. SLI 怎么挑:能采集到不等于该采集

2.1 一个好 SLI 必须同时满足三个硬条件

我筛 SLI 有个很土的办法,拿三个问题去问,全过才算合格。

第一问:它是不是从用户视角出发的?用户关心的是“我点了按钮,页面多久出来、有没有报错”,不关心你内部调了几个服务、走了几层网关。所以 SLI 的采集点应该尽量靠外——入口网关、负载均衡层的位置最合适。你在某个内部微服务上埋点算成功率,那个服务 99.99% 漂亮得很,但上游网关超时把请求掐了,用户照样看到白屏,这个 SLI 就是自欺欺人。

第二问:好和坏能不能一刀切开?好的 SLI 判据清晰,不需要争论。“请求耗时小于 500ms”就是清晰的;“响应比较快”就没法用。有些指标天生模糊,比如“页面加载体验”,就得先定义成一个硬阈值,比如“首屏渲染完成时间小于 2 秒的请求占比”,才能拿来当 SLI。

第三问:能不能按窗口聚合成比例?SLI 最终的用法是“在某个时间窗口内,好事件占总事件的百分比”。所以它必须是可计数的:总请求数、成功请求数、快请求数、慢请求数。如果某个指标只能看瞬时快照,聚合不出比例,那它更适合当监控告警项,而不是 SLI。

三个问题过完,你会发现很多“看起来很重要”的指标被淘汰了,这很正常。SLI 数量贵精不贵多,一个服务三到五个足够,多了反而没人看。

2.2 常见服务类型的 SLI 参考清单

不同类型的服务,用户疼的地方不一样,SLI 的选法也不一样。下面这张表是我这几年攒下来的,新项目起步可以直接照抄再微调。

服务类型推荐主 SLI辅助 SLI不建议当主 SLI 的指标
同步 HTTP 接口成功请求占比(非 5xx 且非超时)快请求占比(P90 < 阈值)平均响应时间、CPU 使用率
异步消息消费消息处理成功占比端到端延迟(入队到落库)< 阈值占比队列堆积条数(只作告警)
数据库/存储读写成功占比慢查询占比(> 100ms)连接数、缓存命中率
定时批处理任务按时完成占比单批次处理时长机器负载
前端页面首屏可用占比(无白屏、无报错)接口错误率、资源加载失败率PV、UV
文件/图片处理处理成功占比处理时长分位值磁盘剩余空间

注意“不建议”那一列,这些指标不是没用,而是不能当主 SLI。它们是诊断工具,出问题时帮我们定位,但不能用来衡量“用户是否满意”。把资源指标写进 SLA 更是灾难,因为资源占用高不等于服务不可用,客户不会为你的 CPU 买单。

2.3 分母陷阱:窗口、剔除和那个最会骗人的平均值

这一节是我最想强调的,因为踩过的坑最多。

先说窗口。同样一次持续 3 分钟的抖动,用 1 分钟粒度采样,会看到 3 个红点,视觉上很刺眼;用 1 小时聚合,那 3 分钟被稀释到 5% 的权重里,几乎看不出来。所以窗口的选择直接决定“你有多容易发现问题”。我的经验是两层窗口并用:长窗口(7 天或 30 天)看趋势、算 SLO 达成率,短窗口(1 分钟或 5 分钟)做告警触发。只用一个窗口,要么太钝要么太吵。

再说剔除。分母里塞进不该算的流量,成功率会虚高得离谱。最典型的是健康检查流量:负载均衡每分钟探测一次,一天就是 1440 次,全是 200,安安静静躺在分母里,把一个真实成功率 98% 的服务“拉”到 99.5%。还有4xx 客户端错误:用户传错参数、爬虫乱扫接口,这些不该算服务端失败,但也不能随手全剔——要区分“用户自己的错”和“我们的接口设计有问题导致的错”。我的做法是维护一张明确的剔除清单,写进 SLO 文档,谁都能查到剔了什么、为什么剔。

最后说平均值,这个指标最会骗人。举个真实算过的例子:100 个请求,95 个耗时 20ms,5 个耗时 3 秒。平均值 = (95×20 + 5×3000) / 100 = 169ms。169ms 看起来岁月静好,但实际情况是 5% 的用户在骂娘。所以延迟类的 SLI 一律用分位值或达标占比,永远别看平均值。我们内部有条不成文的规矩:谁在汇报里写“平均响应时间”,谁请全组喝咖啡。

2.4 埋点位置与采集成本:别把自己埋进坑里

采集点放哪里,直接决定成本。入口层采集成本最低,一次埋点覆盖所有下游,标签也简单(接口名、状态码、耗时)。服务内部采集定位最准,但成本高,每个服务都要改代码,而且标签维度一多就会出现高基数问题——比如把用户 ID、订单号当标签打进去,指标条数瞬间爆炸,存储直接被打穿。

我一般的策略是两层埋点,各司其职:入口层算 SLI 和 SLO,这是“对外口径”;服务内部埋点只用于问题定位,不作为达标判定依据,样本可以采样、可以有损。这样既保证了 SLO 数据的权威性,又不至于让存储成本失控。

还有个细节:埋点必须带一致的接口标识。我见过一个项目,网关层叫/api/v1/order/create,服务内部叫/orderCreate,两边对不上,做归因分析时全靠人工猜,效率极低。定 SLI 之前先把接口命名规范对齐,这一步花半天,后面省几十个小时。

3. SLO 怎么定:从“我要 99.99%”到能落地的数字

3.1 错误预算:把百分比翻译成“每月能坏多久”

盯着 99.9% 这种数字没感觉,得翻译成时间。计算方法很简单:月停机预算 = 30 天 × 24 小时 × 60 分钟 × (1 - SLO)。按 30 天算,一个月是 43200 分钟。

SLO每月允许不可用时长每天允许直观感受
99%432 分钟(7.2 小时)约 14.4 分钟一个月能坏半天多
99.5%216 分钟(3.6 小时)约 7.2 分钟一次大故障就用完
99.9%43.2 分钟约 1.4 分钟一次变更抖动就吃掉一半
99.95%21.6 分钟约 43 秒基本不能有主动故障
99.99%4.32 分钟约 8.6 秒一次自动重启就超了

看完这张表,很多“我要 99.99%”的诉求会自己收回。因为每多一个 9,代价是成倍的:多机房、多副本、灰度发布、自动化回滚、7×24 值守,全得跟上。我在一个项目里算过,从 99.9% 提到 99.99%,机器成本涨了约 1.8 倍,人力投入接近翻倍。所以定 SLO 的正确姿势不是“越高越好”,而是算清楚每提升一个 9 能带来多少业务收益,值不值这个钱。

请求口径也一样。总请求 1 亿次,SLO 99.9%,意味着允许 10 万次失败。10 万次听起来很多,但如果这些失败集中在某个爆款活动的前 10 分钟,用户体验的伤害是灾难级的。所以光看总预算不够,还得看预算消耗速度——这就是所谓的燃烧率:如果按均匀速度消耗,一个月 43.2 分钟的预算,每小时只能烧掉约 1.44 分钟;如果某一小时烧掉了 15 分钟,那燃烧率就是 10 倍以上,必须立刻介入。多窗口燃烧率告警就是基于这个逻辑:长窗口(1 小时或 6 小时)判断“是否真的在快速失血”,短窗口(5 分钟)判断“是否还在持续”,两个都满足才发告警,能有效压掉抖动误报。

3.2 用压测数据倒推容量,再回头校准 SLO

SLO 不是拍出来的,得用容量数据托底。推算链路是这样的:

第一步,估峰值 QPS。拿历史数据看日常峰值,再乘活动系数。比如日常峰值 800 QPS,大促预估流量翻 1.5 倍,那目标峰值就是 1200 QPS。这个系数别怕保守,宁可多留。

第二步,定冗余系数。单副本挂掉、机房抖动、突发流量,都得靠冗余兜。我一般取 1.5 到 2.0,核心链路取高值。

第三步,用压测找单实例拐点。这个最关键。阶梯加压,观察 P99 和错误率,找到“P99 还在阈值内、错误率还没抬头”的那个最大 QPS,这就是单实例安全容量。假设某接口实测在 P99 ≤ 200ms 下,单实例能扛 300 QPS。

第四步,算实例数。需要承载 1200 × 1.6 = 1920 QPS,1920 / 300 = 6.4,向上取整 7 个;再考虑挂一个实例不影响,加 1 个,最终 8 个。这就是 SLO 背后的容量依据。

第五步,反推 SLO 是否成立。如果压测发现,8 个实例下 P99 已经到了 350ms,而 SLI 阈值定的是 200ms,那说明要么降 SLO,要么加机器,要么优化代码。三种选择都行,但必须选一个,不能装看不见。

这里还有一个很多人忽略的换算:并发数与 QPS 的关系。按小定律,并发 ≈ QPS × 平均响应时间(秒)。目标 2000 QPS、平均 RT 50ms,所需并发约 100。压测工具的并发数如果按这个算,往往比“拍脑袋开 1000 并发”靠谱得多,后者只会把被测服务打成一锅粥,测出来的数据毫无参考价值。

3.3 分层定 SLO:核心链路和边缘功能不该一个标准

把所有功能都定成 99.95%,是最常见的偷懒做法,也是最贵的做法。我一般按业务影响分三档:

层级典型功能建议 SLO理由
核心链路登录、下单、支付、扣款99.95%挂了直接损失钱和口碑
重要功能商品详情、购物车、订单查询99.9%影响体验但可短暂降级
边缘功能评论、推荐位、历史浏览99.5%挂了页面还能用
异步任务报表生成、数据同步99%(按天)延迟可容忍,不阻塞交易

分层的好处是可以把有限的资源投在刀刃上。核心链路该多机房就多机房,边缘功能老老实实单机房加降级开关,出了问题一键关掉,用户甚至感知不到。

3.4 一份可以直接抄的 SLO 文档结构

SLO 文档别写成散文,用结构化格式,谁都改得动、看得懂。

service: order-create owner: 交易组 version: 2024-06 slis: - name: availability definition: 入口网关统计的订单创建请求中,返回非 5xx 且未超时(>3s)的占比 source: gateway_access_log exclusions: - 健康检查流量(path=/healthz) - 参数校验失败(4xx,且错误码属于 CLIENT_*) - name: latency definition: 订单创建请求耗时 < 500ms 的占比 source: gateway_access_log note: 按分位值统计,不看平均值 slos: - sli: availability target: 99.95% window: 30d rolling budget_minutes: 21.6 - sli: latency target: 99.0% window: 7d rolling alerts: - type: burn_rate long_window: 1h short_window: 5m threshold: 14.4 action: 通知值班并冻结发布 review_cycle: 每季度

注意exclusions和note这两栏,很多人写 SLO 文档会漏掉,结果半年后没人记得当初剔了什么流量,口径彻底失传。文档里写清“剔了什么、为什么剔”,比写“目标 99.95%”重要得多。

4. SLA 怎么签:把工程指标翻译成合同语言

4.1 一份可执行的 SLA 至少要有四个条款

我见过太多 SLA 只有一句话——“系统可用性不低于 99.9%”——这种协议在真出事时几乎无法执行。一份能落地的 SLA,至少要包含四块内容。

第一块是指标与统计口径。用什么指标(可用性、成功率、响应时间)、什么窗口(自然月还是滚动 30 天)、按什么单位统计(按分钟、按请求数、按接口加权)、哪些情况不计入(计划维护窗口、客户侧网络问题、不可抗力)。这四件事写不全,后面必有争议。

第二块是测量方法与数据来源。数据从哪来、由谁提供、采样频率多少。我一般主张双方共同认可的第三方探针 + 服务方自身的网关日志双轨并行,日常以服务方数据为准,出现争议时以探针数据为准。这条写清楚,能省掉 90% 的扯皮。

第三块是排除项。计划内维护(提前 72 小时通知的)、客户侧配置错误、客户自有网络故障、超出约定流量上限的部分,这些都应该明确排除。不写排除项,等于把所有不确定性都揽到自己身上。

第四块是赔付方式。按未达标幅度分档返还服务费,或者延长服务期。这里有个细节:必须设赔付上限,一般是当月服务费的某个比例。没有上限的赔付条款,签的时候手都在抖。

4.2 缓冲怎么留:SLO 和 SLA 之间那道安全垫

前面说过 SLA 要比 SLO 宽松,具体留多少?我的经验值是至少半个 9 的差距,也就是 SLO 99.95% 对应 SLA 99.9%,SLO 99.9% 对应 SLA 99.5%。这个缓冲的作用是:当内部 SLO 已经在红线附近挣扎时,对外还有一层空间,不至于立刻违约。

缓冲还能倒逼流程。内部一旦触发 SLO 告警但还没到 SLA 违约线,这就是最后的机会窗口——发布冻结、资源加派、根因排查,全都在这段时间里做。等踩到 SLA 线才动手,赔钱已经是既成事实了。

还有个更细的做法:把缓冲期做成台账。每次接近 SLO 上限,就记录一笔,季度评审时看这些记录,能明显看出哪块系统的余量在变薄,提前扩容比事后救火便宜得多。

4.3 统计口径的四个隐形雷区

雷区一:自然月 vs 滚动 30 天。自然月对客户友好(每月重新开始算),但对服务方不友好——2 月只有 28 天,预算天然少一截;而且月末冲量时出问题,影响会被放大。滚动 30 天更平滑,但客户不容易理解。我一般对外用自然月,对内用滚动窗口,两套口径各自服务于不同目的。

雷区二:按分钟算 vs 按请求算。按分钟算(这一分钟内有失败就算这一分钟不可用)对低频接口极不友好——一天只有 100 个请求的接口,一次失败就等于那一分钟全黑,月度可用性直接掉一大截。按请求算更公平,但对高频接口又太宽松。我的做法是:高频接口按请求算,低频接口按分钟算,并且在 SLA 里写清楚分界线。

雷区三:谁来判定“不可用”。是接口返回 5xx 算,还是响应时间超过阈值也算?这两者的差别巨大。很多协议只写“不可用”,实际执行时才发现双方理解不同。必须在协议里给出明确的判定规则,最好附几个例子。

雷区四:争议期与举证责任。出问题后多久内提出异议有效?谁负责举证?我一般会写“客户应在事件发生后 15 个工作日内提出,逾期视为认可服务方数据”。这条看着像法律条文,实际作用很大——能把半年后突然翻旧账的情况挡掉。

4.4 对外承诺前的自检清单

签 SLA 之前,我通常会走一遍这个清单,任何一项打不了勾就不签。

  • 核心链路的 SLI 已经连续观测满 3 个月,数据稳定,口径无争议
  • 内部 SLO 比 SLA 至少严格半个 9,且有台账记录余量变化
  • 压测验证过:峰值流量 × 冗余系数下,SLI 仍能达标
  • 有明确的降级方案和熔断开关,且演练过至少一次
  • 排除项、赔付上限、争议期三项条款已由商务和法务确认
  • 数据来源双方认可,探针部署完成并比对过结果

最后一条特别重要。我见过一次,签完合同才发现双方用的探针部署在不同区域,网络路径都不一样,测出来的可用性差了 0.3 个百分点,光对齐口径就开了三次会。这种事前置成本极低,后置成本极高。

5. 压测实战:把 SLI 打出来,把 SLO 验证掉

5.1 压测之前必须想清楚的三件事

压测最容易犯的错是“上来就打”,打完拿一堆曲线不知道说明什么。开打之前,有三件事必须先定下来。

第一,压测目标是什么。是验证容量(能不能扛住大促峰值)、验证稳定性(连续跑 8 小时有没有内存泄漏)、还是验证降级方案(挂掉一个副本会怎样)?目标不同,场景设计完全不同。我一般把目标写成一句话贴在群里,比如“验证订单创建接口在 2000 QPS 下 P99 不超过 500ms,连续 2 小时错误率低于 0.1%”。

第二,流量模型是什么。真实流量不是均匀的。下单接口的读写比、接口之间的调用比例、用户行为的分布,都得尽量还原。最常见的错误是用单一接口打满,测出来的容量漂亮,一上混合链路立刻崩——因为数据库连接池、线程池这些共享资源在混合场景下争抢激烈得多。

第三,环境是不是隔离的。压测环境混着别人的服务,测出来的数据一文不值。至少要做到:独立的数据库实例、独立的缓存、不共用消息队列。做不到完全隔离,就在低峰期压,并在报告里注明污染风险。

5.2 场景设计:从单接口到混合链路的三级递进

我的习惯是分三级压,每级都有明确的产出。

第一级:单接口基线。只压一个接口,阶梯加量,找到这个接口的单实例拐点。产出是“单实例安全容量”这个数,后面算实例数全靠它。这一级最重要的不是峰值数字,而是拐点前后的形态:什么时候 P99 开始抬头、什么时候错误率开始爬升、两者是不是同步。如果 P99 先抬、错误率不涨,说明是慢查询或锁等待;如果错误率先涨、P99 还好,多半是连接池耗尽或者限流生效。

第二级:混合链路。按真实调用比例混合多个接口,比如下单 30%、查询 50%、其他 20%。这一级能暴露共享资源争抢问题,也是最能反映真实情况的。很多问题在这一级才出现:单接口压的时候缓存命中率 95%,混合压之后降到 70%,因为不同接口的数据互相挤占。

第三级:故障注入。在混合链路跑着的时候,随机摘掉一个实例、制造一次数据库主从切换、模拟一次下游超时。这一级验证的是降级和容错是否真的有效,以及 SLO 在故障期间还能不能守住。我见过不止一次,降级开关写了但从来没测过,真出事时一按下去,返回的数据结构不兼容,前端直接崩。

5.3 压测中盯什么:三个必须实时看的指标

压测过程中,屏幕上至少要同时挂三块东西。

第一块是成功率曲线。掉头向下的那一刻就是拐点。注意要区分 5xx、超时、连接拒绝,这三者的含义完全不同:5xx 是服务端处理出错,超时是处理太慢,连接拒绝是根本连不上(端口、连接数、文件描述符)。我有一次压测看到成功率突然掉到 60%,第一反应是代码有问题,查了半天发现是压测机自身的文件描述符上限到了,虚惊一场。

第二块是 P99 / P999 曲线。只盯平均值会错过所有信号。P99 抬头往往比错误率上涨早几分钟,是最好的预警。如果 P99 从 100ms 缓慢爬到 300ms 而错误率还没动,说明系统已经在排队了,再加一点量就会雪崩。

第三块是被压服务的资源水位。CPU、内存、连接池占用、线程池活跃数、GC 次数。这一块的作用是建立因果链:成功率掉了,是因为连接池满了;连接池满了,是因为某个慢查询占住了连接;慢查询,是因为索引没走对。有了这条链,优化才有方向。

还有个小技巧:压测时把下游依赖的调用耗时也打出来。很多时候压力测试里被测服务本身很健康,但下游某个接口从 20ms 变成 200ms,把整条链路的 P99 拖垮了,这时候优化被测服务毫无意义。

5.4 一份可以改改就用的压测脚本骨架

下面这个用 Python 标准库写的骨架,不依赖第三方包,适合快速验证小流量场景。核心是三件事:阶梯加压、按窗口统计、输出分位值。

import time, statistics, threading, urllib.request, urllib.error from concurrent.futures import ThreadPoolExecutor TARGET = "http://127.0.0.1:8080/api/order/create" DURATION = 60 # 每个档位持续秒数 STEPS = [50, 100, 200, 400, 800] # 并发档位 TIMEOUT = 3 # 超时阈值,与 SLI 定义的阈值保持一致 def one_call(): start = time.perf_counter() ok = False try: req = urllib.request.Request(TARGET, data=b'{"sku":1,"qty":1}', headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: ok = 200 <= resp.status < 300 except Exception: ok = False cost = (time.perf_counter() - start) * 1000 return ok, cost def run_step(concurrency): results = [] lock = threading.Lock() deadline = time.time() + DURATION def worker(): while time.time() < deadline: r = one_call() with lock: results.append(r) with ThreadPoolExecutor(max_workers=concurrency) as pool: for _ in range(concurrency): pool.submit(worker) total = len(results) success = sum(1 for ok, _ in results if ok) costs = sorted(c for _, c in results) p = lambda q: costs[min(int(len(costs) * q), len(costs) - 1)] if costs else 0 qps = total / DURATION print(f"并发={concurrency:4d} QPS={qps:8.1f} 成功率={success/total*100:6.2f}% " f"P50={p(0.50):7.1f}ms P95={p(0.95):7.1f}ms P99={p(0.99):7.1f}ms " f"慢请求占比={(1 - sum(1 for c in costs if c < 500)/total)*100:5.2f}%") if __name__ == "__main__": for step in STEPS: run_step(step) time.sleep(10) # 档位之间留冷却,避免上一档的排队效应污染下一档

几个实操要点:冷却时间不能省,上一档积压的请求会在下一档开头集中释放,导致数据失真;超时阈值必须和 SLI 定义一致,否则算出来的“慢请求占比”和 SLO 口径对不上;输出里要同时给 QPS、成功率、分位值、慢请求占比,因为这四个数要一起看才能判断拐点。这个脚本适合几百 QPS 以内的验证,再往上压测机自身会成为瓶颈,那时候得换分布式压测方案,思路是一样的。

5.5 压测报告怎么写才能支撑 SLO

一份能拿去做 SLO 决策的压测报告,至少要回答四个问题。

第一,单实例安全容量是多少。给出具体数字和判定依据:“P99 ≤ 200ms 且错误率 < 0.1% 的前提下,单实例最大承载 300 QPS”。

第二,满足目标峰值需要几个实例。把计算过程写出来,别只给结论。目标峰值 × 冗余系数 / 单实例容量,向上取整,再加故障冗余。

第三,拐点在哪里,超过会怎样。描述拐点前后的形态变化,比如“超过 320 QPS 后 P99 从 180ms 跳到 900ms,错误率从 0.02% 涨到 4%,说明瓶颈在线程池”。

第四,有哪些风险项和未验证项。诚实写出“本次未验证数据库主从切换场景”“压测环境缓存未隔离”这类限制,比写一堆漂亮结论有用得多。

6. 常见问题与排查实录

6.1 一张速查表覆盖八成疑问

现象可能原因排查方向
SLO 长期 100%,从不告警SLI 口径太宽,或分母混入健康检查查剔除清单、查采样粒度
用户投诉多但 SLI 漂亮SLI 采集点太靠内,未覆盖入口超时把采集点前移到网关
压测 QPS 上不去,CPU 却不忙压测机瓶颈或连接数限制查压测机资源、查文件描述符
P99 突然翻倍但错误率不变慢查询、锁等待、GC 停顿查下游耗时、查 GC 日志
混合压测比单接口差很多共享资源争抢(连接池、缓存)分开看各接口命中率与等待时间
故障注入后降级没生效开关未演练、返回结构不兼容定期演练,校验降级返回
SLA 到期前突然“数据对不上”双方探针口径不一致提前双轨比对,写清争议条款

6.2 五个真实踩过的坑

坑一:把平均值写进 SLO。早期我们的 SLO 写的是“平均响应时间 < 200ms”,结果系统在长尾请求上烂得一塌糊涂,平均值依然很好看。改成“P99 < 500ms 的占比 ≥ 99%”之后,问题立刻暴露出来,也才有了后来的优化方向。

坑二:压测环境带缓存预热,数据虚高。第一次压测前先跑了十分钟预热,缓存全热,测出来的容量比生产高 40%。上线后第一批冷请求打进来,数据库直接被压垮。后来规定:压测必须包含冷启动场景,或者明确标注“本次为热缓存数据”。

坑三:SLA 没写排除项。一次计划内数据库升级,提前发了通知,但因为 SLA 里没写排除条款,客户照样要求按比例返还。金额不大,但流程上非常被动。从那以后,排除项成了必填栏。

坑四:燃烧率告警设太灵敏。一开始用 5 分钟窗口、2 倍燃烧率就告警,结果每天几十条,值班同学直接屏蔽。后来改成双窗口(1 小时 + 5 分钟)、14.4 倍阈值,告警量降到每周一两条,每一条都是真问题。

坑五:SLI 埋点漏了异步回调。下单接口本身成功率 99.98%,但支付回调的成功率只有 97%,两边的 SLI 分开看都还行,合起来看用户体验是断的。教训是:SLI 要覆盖完整业务闭环,不能只盯着第一个接口。

6.3 复盘怎么做才不流于形式

出问题之后的复盘,我坚持三条规矩。

第一,只看时间线,不追责。从第一个异常信号出现,到发现、到定位、到恢复,每一分钟在做什么,全部写清楚。追责会让所有人开始保护自己,信息就断了。

第二,一定要落到 SLI 上。这次故障影响了哪个 SLI、损失了多少错误预算、距离 SLO 红线还剩多少。用预算说话,比用“影响很大”说话有说服力得多。

第三,产出必须是可验证的动作。“加强监控”不是动作,“在网关层增加回调接口的失败率告警,阈值 1%,5 分钟窗口”才是动作。而且要有负责人和截止时间,下次复盘先检查上次的动作有没有做完。

我在实际项目里的体会是,SLI、SLO、SLA 这三样东西,最难的不是定义,是坚持用同一套口径说话。口径一旦漂移,所有数据都会失去可比性,团队就会退回到“我觉得还行”的讨论里。所以我现在做任何稳定性相关的评审,第一个问题永远是:这个数字是怎么算出来的,窗口多长,分母是什么。答不上来,后面的话就不用听了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进