文档目录

1.3 测量点:同一个请求,三个不同的数字

上一节:1.2 百分位不可平均 | 下一节:1.4 SLO 的四要素写法 配套代码:01-metrics-and-slo/03-measurement-points


一句话结论

「这个接口要 200 ms」这句话,缺了最重要的一个信息:在哪测的。 客户端测出 200 ms、网关测出 120 ms、服务端 handler 内部测出 30 ms——三个数字都是对的,但它们回答的是不同的问题。混着用,就会得出错误结论。


二、用快递物流理解「测量点」

你在网上买了一件商品,想知道「多久能到」。可以打卡的地方有很多:

打卡点 记录的时间 用户关心吗
你下单的时刻 起点 ✅ 用户从这里开始等
仓库开始打包 分拣耗时 ❌ 用户不关心,但仓管关心
打包完成、交给快递 打包耗时 ❌ 但能定位「是不是仓库慢」
快递在路上 运输耗时 部分关心
快递到达你所在城市 分拨耗时 ❌
快递员按门铃、你签收 终点 ✅ 用户等的是这个

「送达需要 3 天」这句话:

  • 从「下单」算:3 天。这是用户体验。
  • 从「出库」算:2.5 天。这是物流公司内部的绩效。
  • 从「到达本市」算:6 小时。这是末端配送的效率。

三个数字都对,但你不能拿「末端配送 6 小时」去论证「用户 3 天能收到」。

后端的测量点完全同理。


三、三个测量点

3.1 客户端(压测工具 / 真实用户浏览器 / App)

延迟 = 收到响应的时刻 − 发出请求的时刻

包含:客户端排队 + 网络往返 + 网关 + 服务排队 + 服务处理 + 响应传输。

  • ✅ 这是最接近用户体验的数字。
  • ❌ 受客户端本身影响(压测工具 GC、网卡、临时端口耗尽)。
  • ❌ 如果压测工具用了闭环模型,这个数字会被协调遗漏污染(第 0 章)。

用途:验收 SLO、回答「用户等了多久」。

3.2 网关 / 负载均衡

延迟 = 网关转发响应的时刻 − 网关收到请求的时刻

包含:网关内部处理 + 服务排队 + 服务处理(不含客户端与部分网络)。

  • ✅ 去掉了客户端噪声,适合长期监控。
  • ✅ 能看到网关自身的开销(TLS 握手、鉴权、限流)。
  • ❌ 看不到客户端的网络情况。

用途:生产环境长期观测、网关性能评估。

3.3 应用内埋点(服务端 handler)

延迟 = handler 返回的时刻 − handler 被调用的时刻

只包含服务自身的处理时间,不含网络、不含上游排队。

  • ✅ 最细,可以进一步拆到每个依赖调用。
  • ✅ 是唯一能回答「这 200 ms 花在哪了」的测量点。
  • ❌ 明显小于用户感知的延迟,不能用来验收 SLO。

用途:定位瓶颈、做延迟分解。


四、三个数字的差额,就是你的线索

这是本节最有用的一句话:

客户端延迟 − 网关延迟  = 客户端排队 + 网络往返
网关延迟   − 应用内延迟 = 网关处理 + 服务排队
应用内延迟 − Σ依赖延迟  = 应用自身开销(序列化、计算、锁)

一个具体例子

测量点 P99
客户端 200 ms
网关 130 ms
应用内(handler) 40 ms
└ 依赖:PostgreSQL 25 ms
└ 依赖:Redis 3 ms
└ 依赖:下游 HTTP 5 ms

逐层做减法:

差额 数值 含义
200 − 130 = 70 ms 客户端到网关 网络 + 客户端排队。这部分你优化代码没用
130 − 40 = 90 ms 网关到应用 网关处理 + 服务排队(进不了 handler,说明在等 CPU/线程/连接)
40 − (25+3+5) = 7 ms 应用自身 序列化 + 业务逻辑。这才是你写的代码

结论:

  • 你写的代码只占 7 ms(3.5%)。
  • 最大的两块是「网络」和「排队」。
  • 如果只优化业务代码,最多也只能省 7 ms。

这就是为什么「先测量、后优化」如此重要——大多数人的直觉会让他们去优化那 7 ms。


五、四个必须避免的错误

❶ 拿不同测量点的数据做对比

第 1 周(客户端测得):P99 = 200 ms   ← 基线
第 2 周(服务端测得):P99 = 45 ms    ← "优化后"
结论:性能提升了 77%

这个结论毫无意义——两个数字测的根本不是同一件事。优化前后必须用同一个测量点。

❷ 用应用内延迟验收 SLO

应用内 P99 = 45 ms,看起来完美,但用户实际等了 200 ms。SLO 必须用客户端(或至少网关)视角来定义。

❸ 忽略「服务排队」这一段

「网关 130 ms 减去应用 40 ms = 90 ms」——这 90 ms 里很大一部分是请求在排队等待被处理(等线程、等 CPU、等协程调度)。这部分延迟不在任何一段代码里,所以你在代码里怎么找都找不到。

这正是第 0 章 0.5 节讲的排队效应。 只有饱和度指标(队列深度、池 pending)能看到它。

❹ 埋点位置不对

常见错误:

  • 在过滤器/中间件里计时,但中间件之后还有序列化 —— 漏掉了序列化开销。
  • 只统计成功请求(用成功数做分母)—— 最慢的超时请求恰好被剔除。
  • 在异步/协程场景下,计时器跨越了挂起点但没考虑调度时间(有时这是想要的,有时不是,必须明确)。

六、本节小结

  1. 同一个请求至少有三个都正确的测量点:客户端、网关、应用内。它们回答不同的问题。
  2. 三个数字的差额是最有价值的线索:客户端−网关 = 网络;网关−应用 = 排队 + 网关处理;应用−依赖 = 你自己的代码。
  3. SLO 必须用客户端(或网关)视角定义;应用内延迟只用于定位。
  4. 优化前后必须用同一个测量点,否则对比无效。
  5. 「服务排队」这段延迟不在任何代码里,只能通过饱和度指标发现。

七、自测

  1. 某接口客户端 P99 = 300 ms,网关 P99 = 280 ms,应用内 P99 = 60 ms。请说出你接下来最该查的两件事,并说明理由。
  2. 一个团队报告「接口优化成功,P99 从 250 ms 降到 60 ms」。你追问哪两个问题就能判断这个结论是否可信?
  3. 为什么「应用自身开销 = 应用内延迟 − 各依赖延迟之和」这个减法,有时会算出负数?
  1. ① 查网关到应用之间的 220 ms(280 − 60)去哪了。这个差额巨大,最可能是服务排队(等线程/连接/CPU/协程调度)或网关自身处理慢。要查:网关的 CPU 与连接数、应用的饱和度指标(线程池队列、连接池 pending)、容器 CPU 节流。② 查网关内部的 20 ms(300 − 280):如果是 TLS 握手、鉴权调用或限流计算,相对影响小,优先级低于第一项。注意:应用自身只占 60 ms 的一小部分,优化业务代码收益有限。
  2. ① 测量点是什么?优化前后是否一致? 如果基线是客户端测得、优化后是服务端测得,那 250→60 是测量点的差别,不是优化效果。② 负载前提与样本量是什么? 多少 QPS、什么数据量、跑了几轮、错误率有没有变化(可能把慢请求变成了错误请求)。
  3. 因为并行调用。如果应用内同时调用 PostgreSQL(25 ms)和 Redis(3 ms),总耗时不是 28 ms 而是 max(25, 3) = 25 ms。此时「40 − (25+3) = 12」就低估了自身开销;如果依赖调用之间有重叠、或者计时器覆盖范围与依赖计时器不一致,甚至可能算出负数。正确做法:串行链路上可以直接相减;并行链路上要用实际的关键路径(或直接用 tracing 的 span 树)来分析。