1.4 SLO 的四要素写法
上一节:1.3 测量点 | 下一节:1.5 延迟预算 配套代码:01-metrics-and-slo/04-writing-slo
一句话结论
「P99 < 200 ms」不是一个 SLO,只是一半句话。 完整的 SLO 必须包含四要素:指标 + 阈值、负载与数据前提、测量点、持续时长。少了任何一项,验收时都会吵架(而且通常是上线后才吵)。
一、用「验收合同」理解 SLO
假设你要装修房子,和施工队签合同。你在合同上写:
「装修要好。」
施工队会问你一堆问题:
| 你的原话 | 施工队必须追问 | 对应 SLO 的要素 |
|---|---|---|
| 「装修要好」 | 「哪方面好?水电?墙面?隔音?」 | 指标 |
| 「墙面要平」 | 「多平算平?误差几毫米?」 | 阈值 |
| 「要平」 | 「房子多大?什么结构?现在什么状况?」 | 负载与数据前提 |
| 「要平」 | 「用谁的尺子量?从哪量到哪?」 | 测量点 |
| 「要平」 | 「验收时看一次,还是住半年后还得平?」 | 持续时长 |
SLO 就是你和系统签的验收合同。 一份只有「装修要好」的合同,结局一定是扯皮。
二、四要素详解
要素 1:指标 + 阈值
必须明确是哪个指标(P99?P95?错误率?吞吐?)和具体数值。
| ❌ 模糊 | ✅ 明确 |
|---|---|
| 「响应要快」 | P99 < 200 ms 且 P999 < 500 ms |
| 「要稳定」 | 错误率 < 0.1%,超时率 < 0.05% |
| 「能扛住」 | 稳定支撑 500 RPS |
要点:延迟类 SLO 至少写两个分位数(P99 + P999,或 P95 + P99)。只写一个分位数时,你不知道「剩下那 1% 有多糟」。
要素 2:负载与数据前提
这是最常被忽略、也是争议最大的一项。
同一份代码,在不同负载下的表现可以差几个数量级:
| 前提 | 为什么必须写 |
|---|---|
| QPS / 并发模型 | 1 RPS 和 10000 RPS 是两个世界(第 0 章的排队效应) |
| 数据量 | 1 万行可能全表扫描,1 亿行走索引——执行计划不同,结论不可迁移 |
| 数据分布 | 均匀分布会让缓存命中率虚高、锁竞争虚低;真实业务是幂律分布 |
| 缓存状态 | 冷缓存(发布后)与热缓存的 P99 可以差几十倍 |
要素 3:测量点
必须写明数据是在哪测的(见 1.3 节):
- 从压测客户端观测(最接近用户)
- 从网关观测
- 从应用内埋点观测(只代表服务自身处理时间)
这三个数字不能互换,所以合同里必须写清楚「用谁的尺子量」。
要素 4:持续时长与验收方式
| 必须写 | 为什么 |
|---|---|
| 持续多久 | 跑 30 秒可能只是 JVM 冷启动阶段,跑 10 分钟才进入稳态 |
| 预热多久不计入 | 预热期数据混入统计会让 P99 虚高 |
| 取几轮、取哪一轮 | 跑 3 轮取中位数,还是取最好的一轮?(后者是自欺) |
三、完整 SLO 的写法示例
❌ 不完整(常见的写法)
订单查询接口 P99 < 200 ms。
✅ 完整
订单查询接口(
GET /orders/{id}) 在 500 RPS 恒定到达率、1000 万行订单数据、幂律访问分布(前 1% 用户占 30% 请求)、缓存预热 5 分钟后 的条件下: 从压测客户端观测,稳态维持 10 分钟:
- P99 < 200 ms
- P999 < 500 ms
- 错误率 < 0.1%
- 单实例 4C8G,无外部缓存
对比一下:第一句话用的是「读一遍就懂,但没法验证」;第二句话任何人都能照着复现。
建议同时定义一条「崩溃线」
SLO 是「合格线」,但压力测试还需要一条「终止线」:
降级判定:当 P99 > 1 s 或错误率 > 1% 持续 30 秒,视为不可接受,压力测试应停止并记录此时的负载与状态。
有了这条线,压力测试才有明确的终点,也才能测出「崩溃点」在哪里。
四、SLO 不是越快越好
这一节要防一个常见误区:把 SLO 定得极其严格。
| 后果 | 说明 |
|---|---|
| 成本暴涨 | P99 从 200 ms 压到 50 ms,可能要多加 3 倍机器或引入复杂缓存 |
| 团队疲于救火 | 无法达标 → 告警天天响 → 所有人开始忽略告警 |
| 掩盖真正的目标 | 用户可能根本感知不到 150 ms 和 200 ms 的差距 |
SLO 应该定在「用户能感知到差别的那条线」附近,而不是「技术上能压到多快」。
判断方法:问业务方「如果这个操作要等 500 ms,用户会怎样?」「1 秒呢?」「2 秒呢?」——通常能问出一个「再慢就会有明显流失」的临界点,SLO 就设在它稍前面一点。
补充概念:SLO 对应一个「错误预算」——如果 SLO 是 99.9%,那一个月内允许有 0.1% 的请求违约。这个预算可以被「花掉」,也是第 8 章告警策略的基础。
五、SLO 表模板
| 字段 | 示例 |
|---|---|
| 接口 | GET /orders/{id} |
| 关键路径 | 客户端 → 网关 → 订单服务 → PostgreSQL |
| 目标 QPS | 500(峰值),恒定到达率模型 |
| P50 / P95 / P99 / P999 | 20 / 60 / 200 / 500 ms |
| 错误率阈值 | < 0.1% |
| 负载前提 | 1000 万行数据,幂律分布,缓存预热 5 分钟 |
| 测量点 | 压测客户端 |
| 持续时长 | 稳态 10 分钟 |
| 环境 | 单实例 4C8G,无外部缓存 |
| 崩溃线 | P99 > 1 s 或错误率 > 1% 持续 30 秒 |
| 测量日期与版本 | 2025-xx-xx,commit abc1234 |
六、本节小结
- SLO 的四要素:指标 + 阈值、负载与数据前提、测量点、持续时长。缺一项就无法验收。
- 负载前提是最常被忽略、也最容易引发争议的一项(QPS、数据量、数据分布、缓存状态)。
- 延迟类 SLO 至少写两个分位数,并单独列出错误率。
- 建议同时定义一条「崩溃线」,作为压力测试的终止条件。
- SLO 不是越快越好——定在「用户能感知的临界点」稍前面,否则成本和告警噪声会失控。
七、自测
- 下面这条 SLO 缺了哪些要素?
「用户服务接口的响应时间不超过 100 ms,错误率低于 0.1%。」
- 为什么「数据分布」必须写进 SLO 前提?请举一个它会导致结论完全不同的例子。
- 一个团队把 SLO 定成「P99 < 10 ms」,结果天天告警、天天加班。请说出这个 SLO 可能的三个问题。
- 缺:① 负载前提(多少 QPS?数据量多大?缓存是否预热?)② 测量点(客户端 / 网关 / 应用内?)③ 持续时长与验收方式(跑多久?预热多久?取几轮?)④ 指标不够明确(「响应时间不超过 100 ms」是 P50 还是 P99?还是最大值?)。⑤ 没有说明环境(单实例规格?副本数?)。
- 因为分布决定了缓存命中率和锁竞争程度。例子:一个「按用户 ID 查订单」的接口,用均匀分布压测时,每个 key 访问概率相同,缓存命中率可能只有 30%(因为 key 太分散);而真实业务是幂律分布(少数活跃用户占了大部分请求),缓存命中率可能达到 85%。两者的 P99 会差好几倍——用均匀分布测出的「达标」,在真实流量下可能完全不达标(反之也可能过度悲观)。
- ① 可能远超用户感知需求:用户根本区分不出 10 ms 和 50 ms,这个 SLO 是在追求数字而不是体验;② 可能超出性价比:为了从 50 ms 压到 10 ms 需要引入复杂缓存、加机器、做预计算,成本可能翻几倍;③ 没有区分场景:所有接口一刀切,而实际上「下单」和「查历史订单」的用户容忍度完全不同。正确做法:按接口分级(例如核心链路 P99 < 100 ms、一般查询 P99 < 300 ms),并让告警基于错误预算(第 8 章),而不是瞬时超标。