卷 I · 排起来CH 04深度 4/24

「平均等 3 分钟」几乎没有信息

排队时间不是围着平均值上下抖动的。它是一堆零,加上一条又长又薄的尾巴。你的用户体验、你的超时设置、你的告警阈值,全都住在那条尾巴上——而平均值恰好是唯一一个看不见尾巴的统计量。

分布不是平均值指数尾p99 从哪来

▷ 先估一个数

一条队,利用率 80%,到达和服务都是随机的。平均等待是 4 个服务时长(第 1 章那个数)。

现在问:有多大比例的人,等待时间低于这个平均值?

A 大约一半。平均值嘛,两边差不多 B 大约 63% C 大约 80% D 大约 95%,只有极少数人被平均值代表

顺手再估第二个:这条队的 p99 等待是平均值的几倍?

平均值是怎么骗人的

「平均等待 4 个服务时长」这句话在你脑子里长什么样?

大概是这样:大部分人等 3 到 5,少数人等 2 或 6,极少数运气特别差的等到 8。一个钟形的东西,围着 4。

真实形状完全不是这样。

ρ=0.8 时的真实分布是这样的:

  • 20.0% 的人等待恰好是零——他们来的时候机器正好空着,直接开始。这不是「等得很少」,是「一点没等」。
  • 中位数是 2.350,只有平均值的一半多一点。
  • p90 是 10.397
  • p99 是 21.910——平均值的 5.48 倍
  • p99.9 是 33.423
  • 而在三十万次模拟里,最惨的那一位等了 49.7

把这些数字放在一起看:「平均 4」这个数,同时代表着「五分之一的人是 0」和「百分之一的人是 22」。它落在两群人中间的空地上。

而且有 63.2% 的人低于平均值。这个比例本身就说明了形状——在一个对称分布里它应该是 50%。

◆ 等待时间的形状

M/M/1 队列的等待时间有一个非常干净的闭式解,它把上面所有数字都解释清楚了:

P(等待 = 0)   = 1 − ρ                      ← 一大块概率质量堆在零上
P(等待 > t)  = ρ × e^(−(μ−λ)t)             ← 剩下的是一条指数尾

这是一个混合分布:一部分是「点质量」(一大堆人的等待精确为零),一部分是「连续的指数尾」。它长得一点都不像钟形,也没有任何理由长得像钟形。

而指数尾的意思是:它衰减得很慢,而且没有硬上限。永远存在一个更倒霉的人。

为什么会有一条指数尾

这不是数学巧合,背后有一个很物理的机制,叫忙期(busy period)。

队列的一生是这样的:空 → 来了一个人 → 在他被服务的时候又来了人 → 队排起来 → 一直排到某个时刻,队终于空了 → 空 → 又来一个人……

中间那段「一直不空」的时间叫一个忙期。关键在于:忙期的长度是自我放大的。队越长,清空它需要的时间越久;而清空它的时间越久,期间来的人就越多,队就更长。

# 一个忙期的样子(ρ = 0.8)
   空 ▏◉ ◉◉ ◉◉◉ ◉◉◉◉ ◉◉◉ ◉◉ ◉ ▏空 ▏◉ ▏空空空 ▏◉◉◉◉◉◉◉◉◉◉◉◉ …
      └──── 一个普通忙期 ────┘   └短┘      └──── 一个倒霉的长忙期 ──…

# 落在长忙期里的人,等待时间是长忙期里所有人一起决定的
# 而长忙期出现的概率虽然低,却不会低到可以忽略 —— 那就是那条尾巴

ρ 越接近 1,忙期越难结束,尾巴就越长。这也解释了 p99 的来源:p99 不是「运气差」,它是「掉进长忙期」的概率。而长忙期出现的概率完全由 ρ 和变异性决定——它可以算,也可以设计。

∑ 算一遍:从平均值直接算出 p99

M/M/1 的等待时间分位数有闭式解,你可以拿计算器直接用:

# 想知道 p 分位的等待时间(p 要大于 1−ρ,否则答案就是 0)
Wq(p) = ln( ρ ÷ (1−p) ) ÷ (μ − λ)

# 举例:ρ=0.8,一次服务 1 个时间单位(μ=1,λ=0.8)
Wq(0.50) = ln(0.8 ÷ 0.50) ÷ 0.2 = 2.350
Wq(0.99) = ln(0.8 ÷ 0.01) ÷ 0.2 = 21.910
Wq(0.999)= ln(0.8 ÷ 0.001) ÷ 0.2 = 33.423

# 注意 ln 里面:分位数越靠尾,只是对数式地变大。
# 这是好消息也是坏消息 ——
#   好消息:从 p99 到 p99.9 只多了 11.5,不是十倍。
#   坏消息:那个 (μ − λ) 在分母上。它就是余量。余量减半,整条尾巴翻倍。

最后那句话值得记住:所有分位数都被同一个分母 μ − λ 缩放。这个分母就是「每单位时间能多消化多少活」,也就是余量。你不需要分别优化 p50、p95、p99——它们是同一根杠杆上的刻度。

该看哪个数

那么该报什么?这里有一个实践上的排序,从最没用到最有用:

  1. 平均值——几乎唯一的用途是喂进 Little 定律。不要用它做 SLA,不要用它做告警。
  2. 中位数(p50)——回答「典型的一次体验是什么样」,比平均值诚实得多。
  3. p99 / p99.9——回答「最糟的那部分体验是什么样」。这是用户投诉的来源,也是超时该设在哪的依据。
  4. 整条直方图——唯一能让你看出「是一条尾巴,还是两个峰」的东西。两个峰意味着你的流量里混了两类完全不同的请求,那是另一个故事(第 18 章、第 19 章)。

另外还有一个特别值得报的数,几乎没人报:「不用等的人占多少」。它精确地等于 1 − ρ,也就是余量。这个数是「有多满」和「用户体验」之间最直白的一座桥:余量 20%,就有 20% 的人享受到零等待。

✎ 术语正名:「长尾」

「长尾」这个词在产品和商业语境里被用成了褒义(长尾市场、长尾需求)。在这里它是纯粹的成本

而且要区分两种不同程度的尾:

  • 指数尾(本章这条):衰减得慢,但方差是有限的,公式算得出来。
  • 重尾 / 幂律尾(第 5 章会见到):衰减得更慢,方差可以是无穷的。此时「平均值」不但没用,连「方差」这个概念本身都失效了,本书大部分公式在那里直接不成立。

真实的软件系统里,请求耗时经常是重尾的(想想一个查询扫了全表)。所以这本书的公式在实战中往往是乐观的下界。

▸ 在现实里

为什么你的超时总是设错。常见做法是「按平均延迟的 3 倍设超时」。在 ρ=0.8 时,平均值的 3 倍大约落在 p95 附近——意味着你主动放弃了 5% 的请求,而且是在系统完全健康的时候。更糟的是,超时会触发重试,重试增加负载,负载推高 ρ,ρ 推长尾巴,更多请求超时。这个正反馈叫重试风暴,第 21 章会算它的账。

为什么「99% 的用户没受影响」是错的说法。一次用户操作往往触发几十个后端请求。每个请求 p99,几十个一起,用户碰到至少一个慢请求的概率高得多——第 16 章会算出那个数字,它比大多数人猜的大得多。

地铁换乘。「平均换乘等待 3 分钟」对通勤族毫无意义。他们规划时间用的是「最坏情况」,因为迟到的成本是不对称的。凡是成本不对称的场景,平均值天然就是错的统计量。

✗ 这个直觉是错的
p99 是极端情况,是运气不好的少数人,可以先不管——先把平均值优化好。 p99 和平均值是同一个分布的两个刻度,被同一个分母 μ − λ 缩放。你没法单独优化平均值而不动 p99,也没法只治 p99 而不动余量。

更重要的是:p99 才是那个决定用户是否留下的数。一个每天用 100 次的功能,p99 = 1% 意味着用户平均每天碰到一次糟糕体验。他不会记得那 99 次顺畅,他会记得那一次。

还有一个陷阱:很多人以为「优化尾部」需要专门的技术手段。其实第 10 章会证明,在高利用率区间,只要加一点点产能,整条分布(包括尾巴)会一起往下掉——而且加的量少得让人吃惊。

◇ 结算

B大约 63%。模拟三十万个请求量出来是 63.2%。而第二问的答案是 5.48 倍:p99 是 21.910,平均值是 4.000。

63% 这个数不是巧合。指数分布有一个著名的性质:低于均值的概率恰好是 1 − 1/e ≈ 63.2%。等待时间的尾巴是指数的,所以这个数字在这里冒了出来。

A 「大约一半」——这是把分布默认成对称的。凡是「有下界(不能小于 0)、没有上界」的量,几乎一定是右偏的,中位数必然小于平均值。延迟、收入、文件大小、城市人口,全都是这个形状。 C 「大约 80%」——80% 这个数确实在这道题里出现,但它是利用率,不是这个比例。真正等于 1 − ρ = 20% 的,是「等待恰好为零的人的比例」。 D 「大约 95%」——高估了偏斜程度。指数尾虽然长,但它比幂律尾温和得多;要到 95% 这个程度,得是第 5 章那种重尾分布。

这一章的一句话

排队时间是「一大堆零 + 一条指数尾」,平均值恰好落在没人住的地方;而所有分位数共用一个分母,那个分母就是余量。

卷 I 到此结束。你已经知道了排队有两个成因,也知道了它的形状。下一卷专攻第二个成因:不齐。第 5 章先把它变成一个可以算、可以量、可以挂在监控上的数——你会看到一个 等于 7.424 的分布(90% 快 + 10% 慢一百倍),以及一个 等于无穷的分布,而后者在真实系统里一点都不罕见。