qinghuan
← 回到知识探索

后端现场 / 动手实验

平均只要三十毫秒,为什么有人等一秒?

用两组假设请求比较平均值和分位数,看看慢请求怎样被平均掉。

展开本文目录
  1. 先动手看看
  2. 先把请求排成一列
  3. 用同一个平均值造出不同体验
  4. 用户等的是整件事完成
  5. 几台机器的 p99 不能直接求平均
  6. 记住这三点
  7. 别把例子用过头
  8. 自己试一小步
  9. 查原始资料
  10. 找一份资料继续练
开始前,会这些就够了
  • 会读取简单列表和平均数
遇到不熟悉的词,先看这 2 个解释
吞吐量
单位时间内处理的工作数量,例如每秒完成多少请求或每分钟处理多少任务;比较前要写清统计窗口、工作类型,以及失败的尝试是否也被计入。
延迟与 p99
延迟是一次操作从指定起点到终点经历的时间;把多次耗时排序后,p99 描述接近百分之九十九位置的耗时,用来观察较慢请求的情况。
打开完整术语小抄 ↗

假设两套接口的平均响应时间都接近三十毫秒。第一套每次都差不多快;第二套大多数时候很快,却偶尔停顿一秒。平均数把这些差异压成一个数,所以“平均相同”并不等于用户体验相同。下面的数据全是人为构造,用来观察统计量,不是某个服务的测评。

只看平均数,会漏掉谁?

平均
29.8 ms
p95
10 ms
p99
1000 ms
最大值
1000 ms

试着把慢请求从 20 个改成 10 个,再改成 1 个。p99 还会不会显示最慢的那次?

固定 1,000 个假设请求,普通请求为 10 毫秒。分位数采用最近秩法(向上取整位置),没有监控采样或直方图估计。这里的数字不是服务性能测量。

先把请求排成一列

把响应时间从短到长排序,就能问:排在百分之九十五位置的请求用了多久?这个值常写成 p95。它是一个时间阈值,不是“最慢百分之五请求的平均时间”。不同软件可能采用插值等不同定义。本实验先把样本数乘以 0.95,将结果向上取整,再取排序后对应位置的值;位置从 1 开始计数。

分位数要带着请求数量和时间范围一起读。一小时的 p99 不能直接代表某一分钟,几十个请求得出的高分位数也可能非常不稳定。它帮助描述慢尾巴,但不会自动解释原因;排队、锁等待、磁盘、网络或下游调用,都需要更多证据区分。

用同一个平均值造出不同体验

模型 A 的一千个请求都耗时二十九点八毫秒;模型 B 有九百八十个请求耗时十毫秒,其余二十个耗时一千毫秒。两者总时间相同,因此平均数相同。运行程序时,关注 p95 和 p99 是否给出了同样的判断。

这里直接保存全部样本并排序,得到的是这份有限列表按指定定义计算的结果。线上监控通常不能无限保存所有原始请求,会用直方图等结构统计分布,算出的分位数可能是估计值,精度取决于所用算法和配置。

小实验 · python
from math import ceil

samples = {"A": [29.8] * 1000, "B": [10] * 980 + [1000] * 20}

def percentile(values, p):
    ordered = sorted(values)
    return ordered[ceil(p * len(ordered)) - 1]

for name, values in samples.items():
    print(name, "mean", round(sum(values) / len(values), 1),
          "p95", percentile(values, 0.95),
          "p99", percentile(values, 0.99))

怎样运行:Python 3.10+;保存为 latency_demo.py,运行 python latency_demo.py。

运行后对照结果
A mean 29.8 p95 29.8 p99 29.8
B mean 29.8 p95 10 p99 1000

用户等的是整件事完成

一次页面展示可能同时请求商品、库存和推荐。如果页面必须等它们全部完成,最慢的那个就可能决定这一轮等待时间。调用次数越多,某次慢请求参与其中的机会也会改变;但不能不看依赖关系,就把各接口的 p99 相加或相乘当成页面 p99。

也要区分快速失败和快速成功。若大量请求立即报错,平均响应时间反而可能变小,系统却更不可用。观察时同时记录成功率、超时数量和端到端耗时,并说明计时是否包含连接池等待、重试与客户端网络。少记了一段等待,图表仍会很好看。

几台机器的 p99 不能直接求平均

每台机器的 p99 只是它自己分布上的一个点,没有告诉你其他请求在哪里。把这些点平均,无法恢复所有请求混在一起后的排序。请求量不同还会让问题更明显:一台只接十个请求,另一台接十万个,它们也不该在整体平均耗时里被等权看待。

需要整体分位数时,保留可合并的分布信息,例如兼容的直方图桶;需要整体平均值时,汇总耗时总和与请求总数后相除。最后按真实目标观察“多少成功请求在规定时间内完成”,让数字对应到用户完成的动作。

记住这三点

  • 平均相同的两组请求,可以有完全不同的慢请求分布。
  • p99 是分布位置对应的阈值,要说明算法、样本数和时间范围。
  • 跨实例汇总分位数需要分布信息,不能直接平均各自的 p99。

别把例子用过头

  • 人为列表只能解释统计关系,不能作为吞吐量或真实性能证据。
  • 本例的排序分位数和监控平台的估计值可能不同。
  • 只统计成功且未超时的请求,可能漏掉用户等待最久的部分。

自己试一小步

如果只剩一个请求耗时一千毫秒,p99 还一定能揭示它吗?

  1. 保持一千个请求总数,改变慢请求数量并记录平均值、p95、p99。
  2. 再加一个最大耗时指标,比较它与 p99 的敏感程度。
  3. 写出你为“用户等太久”选择的两个指标和观察窗口。

做到这些,就算完成

  • 能用“样本数乘以所求比例,再向上取整”独立检查计算位置。
  • 没有把 p99 当成最坏情况保证。
  • 指标中包括请求量或成功/失败信息。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • Monitoring Distributed Systems (新标签页)↗Google SRE · 英文

    支持观察延迟分布、区分成功与失败延迟,以及延迟、流量、错误和饱和度。

    公开英文 SRE 书籍章节;适合理解指标,不提供适用于所有服务的延迟阈值。

  • Histograms and summaries (新标签页)↗Prometheus · 英文

    支持分位数与直方图、估计误差、跨实例不能直接平均分位数和总和/计数平均。

    公开英文机制说明;分位数估计与合并方式取决于指标类型和具体实现。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。