目标
平均延迟描述快乐路径,SLO 却住在 p99——那里由完全不同的原因主导:缓存未命中、慢分片、重试放大。把尾部分解成组件跨度,找出拥有爆表的那一个,只修它就能动 SLO。二峰分布的跨度(命中与未命中)几乎总是违规所在地。
步骤要点
- 从分布现实定 SLO:按当前分布与用户期望给每类端点定分位数目标,选积极但不虚构的 p95 与 p99。
- 把尾部分解为跨度:对 p99 违规的端点把请求拆成跨度(校验、下游调用、序列化),按尾部贡献排名。
- 调优主导组件:修尾部贡献最高的——并行独立调用、可重复处加缓存、削减载荷或收紧下游超时。
- 在尾部而非均值验证:部署后对端点对比 p99 与 p99.9,观察至少一个含峰值的完整流量周期。
- 用告警与预算守住收益:给调优过的端点接 SLO 燃烧告警,记录达成的基线供下一轮使用。
排错提示
命中率 95% 听起来很好却独占 p99,未命中的 5% 各付全额源站成本,看二峰分布的跨度;单项调用 180ms 看着能忍,二十个条目 N+1 放大后 p99 继承二十次抽样最差的那个;只报均值改善是自欺,验证指标必须是对齐 SLO 的那个分位数。
适用边界
跨度分解依赖埋点的完整性,未打点的黑盒调用是归因盲区;尾部行为受流量结构影响,峰值周期内的验证不可省略。
代码示例
endpoint: GET /api/feed SLO: p99 <= 500ms
field: p99 = 2.4s (violating), p50 = 90ms
span decomposition (p99 contributions):
auth.token 8ms (flat)
feed.query 310ms (cache-hit: 4ms)
cache-miss p99: 900ms <- DOMINANT
media.presign 180ms (per-item, N+1)
serialize 22ms
fixes (ranked by tail contribution):
1 feed.query: warm + stale-while-revalidate
(miss rate 11% -> 0.4%) p99 span: 900 -> 60ms
2 media.presign: batch + 5min cache 180 -> 21ms
after (7d incl. peak): p99 = 380ms SLO: MET