卷 II · 不齐CH 06深度 6/24

随机,长得像成团

人对随机的直觉有一个非常稳定的偏差:我们觉得随机应该「均匀」。于是真正的随机看起来像有规律,而我们造出来的假随机看起来才像随机。这个偏差直接导致了容量规划里最常见的一类错误。

泊松过程成团峰值不是平均值

▷ 先估一个数

你的服务平均每秒收到 1 个请求,到达是完全随机的(互不影响、没有规律)。

观察二十万秒。请问:有多大比例的秒钟,一个请求都没有?

A 几乎没有。平均每秒 1 个,空秒应该很罕见 B 大约 10% C 大约 37% D 大约 50%

再顺手估一个:观察二十万秒,最挤的那一秒来了几个?

先做个测试

下面有两排竖线,每一条线是一次到达。一排是真正的随机,另一排是「均匀撒开再抖一抖」的假随机。两排的到达次数完全相同。

先看,再猜,最后点揭晓。

大多数人会挑 B 排。因为 A 排里有明显的「几个挤在一起」和「一大段什么都没有」,看起来像被安排过的——而 B 排间距均匀,看起来才「随机」。

答案是 A 排

这是一个非常稳定的认知偏差,心理学上叫群集错觉(clustering illusion)。我们的大脑把「随机」理解成了「没有聚集」,但真正的随机一定会聚集——因为「不聚集」本身就是一种规律,而规律恰恰是随机不允许的。

随机到达长什么样

「到达完全随机」这句话在数学上有个精确的名字:泊松过程。它的定义只有两条:

  1. 每一小段时间内到达的概率,只和这段时间的长度有关,和它在哪里、之前发生过什么都无关;
  2. 不重叠的两段时间里,到达是互相独立的。

就这两条。它们听起来无害,但推出来的结果是:

# 每单位时间到达数的分布(λ = 平均每单位时间几个)
P(这一秒来了 k 个) = e^(−λ) × λ^k ÷ k!

# λ = 1 时:
P(0 个) = 36.8%      ← 超过三分之一的秒钟,一个人都不来
P(1 个) = 36.8%
P(2 个) = 18.4%
P(3 个) = 6.1%
P(4 个) = 1.5%
P(5 个) = 0.3%

# 到达间隔的分布:指数分布,c² = 1
# ★ 泊松过程 = 到达间隔是指数分布 —— 这两句话说的是同一件事

模拟二十万秒,实测是 36.9% 的空秒(理论 36.8%),26.3% 的秒钟来了两个以上,而最挤的那一秒来了 8 个——是平均值的八倍。

◆ 「平均每秒 1000 个」不能告诉你要准备多少机器

假设你按「平均每秒 1000 个」配了刚好能处理 1000 个/秒的产能。按泊松过程,会发生什么?

  • 大约 36.8% 的时间里,来的比 1000 少,产能空转(而空转的产能是存不住的);
  • 剩下的时间里来的比 1000 多,多出来的部分进队;
  • ρ 精确等于 1,队列无限增长

这就是第 1 章那句话的机制版本:产能是易腐品。省下来的那 36.8% 不能存到高峰用,但高峰多出来的活会一直记在账上。所以「按平均值配产能」在数学上等价于「让队列无限长」。

为什么偏偏是泊松

泊松过程在排队论里的地位近乎默认假设,这需要一个理由——毕竟真实的用户行为不可能真的「完全无记忆」。

理由是叠加定理很多个互相独立的、各自不规则的到达流叠加起来,会收敛到泊松过程,不管每一个单独的流长什么样。

这和中心极限定理是同一类结果。一个用户的行为一点都不随机——他有作息,会连续点几下,会走开半小时。但十万个这样的用户叠在一起,整体就变成了泊松的。

这条定理解释了一件事:你的服务用户越多,到达越接近泊松,而 ca² 就越接近 1,压不下去了。换句话说,到达的不齐是你几乎控制不了的那一半——所以第 8 章之后这本书的重心会移到你能控制的那一半:服务的不齐、利用率、调度。

∑ 算一遍:泊松的三个反直觉推论

1. 拆开不会变整齐。把一个泊松流按概率随机分成两份(比如按 50% 分流到两台机器),每一份仍然是泊松的。负载均衡不会降低 ca²

2. 合并也不会变不齐。两个独立泊松流合并,还是泊松。所以「把两个服务合并部署会不会让流量更抖」——不会。

3. 已知这一分钟来了 n 个人,他们的到达时刻在这一分钟里是均匀分布的。这条经常用来做压测:要模拟泊松到达,可以先按泊松抽这一秒来几个,再把它们随机撒在这一秒里。

// 生成泊松到达的最简写法:间隔取指数分布
let t = 0;
while (t < duration) {
  t += -Math.log(Math.random()) / lambda;   // ← 就这一行
  arrivals.push(t);
}

如果你的压测工具是「每 10 毫秒发一个请求」,那你测的是 ca²=0 的世界,比生产环境温和得多。这大概是压测结果总比线上好看的最常见原因。

✎ 术语正名:「峰值流量」

「我们的峰值是平均值的 3 倍」——这句话必须问一句:峰值是按什么时间粒度算的?

同一份流量,按小时看峰谷比可能是 3,按分钟看是 5,按秒看是 12,按 100 毫秒看是 30。粒度越细,峰值越高,而且没有尽头。

这不是测量误差,这是泊松过程的性质:越短的窗口,相对波动越大(方差和均值成正比,所以相对标准差是 1/√λ)。

实践上的含义:你该用哪个粒度,取决于你的队列能吸收多长时间的失衡。一个能缓冲 10 秒的系统,看秒级峰值是浪费;一个同步阻塞、没有队列的系统,得看毫秒级。

▸ 在现实里

二战期间伦敦的 V-1 导弹落点。当时伦敦人普遍相信德军在精确瞄准某些区域,因为有些街区被炸了好几次,有些一次没有。战后统计学家把伦敦分成 576 个格子,检验落点分布——完美符合泊松分布。那些「明显的目标区域」只是随机聚集。这是群集错觉最著名的案例。

缓存雪崩。大量 key 设了相同的 TTL,于是它们同时过期,同时回源。这不是泊松了——这是比泊松还糟的成团。标准解法是给 TTL 加随机抖动,本质上就是人为把 ca² 压回 1 附近

定时任务。所有人都把 cron 设在整点,于是每小时的第 0 秒是一天中最危险的时刻。同样的解法:加抖动。很多云厂商的托管定时任务默认就带随机偏移,理由就是这个。

你的手机推送。一次推送带来的流量峰值远远超过泊松——因为它破坏了独立性:所有人在同一时刻被同一个事件触发。这类流量叫「相关到达」,它的 ca² 可以远大于 1,而这本书的公式在这里会低估等待。

✗ 这个直觉是错的
用户行为不是随机的,他们有明显的作息规律,所以泊松模型不适用于我的系统。 作息规律决定的是 λ 随时间怎么变;在任何一个「λ 大致不变」的短窗口里,大量独立用户叠加的结果仍然是泊松的。这两件事在不同的时间尺度上,互不矛盾。

正确的用法是分段:把一天切成若干段(比如每 15 分钟),每段里 λ 视为常数,在段内用泊松模型。这叫「非齐次泊松过程」,是容量规划的标准做法。

反过来说,有一类系统确实不能用泊松:到达之间有强相关性的。典型的是:

  • 重试——一个失败会立刻带来更多请求,而且是在系统最不行的时候;
  • 推送 / 广播——一个外部事件同时触发所有人;
  • 批量作业——一次提交一万条。

这三类的共同点是正相关,它们的 ca² 会远大于 1。而在真实的线上事故里,它们几乎总是主角。

◇ 结算

C大约 37%。理论值是 e^(−1) = 36.8%,二十万秒的模拟量到 36.9%

第二问:最挤的那一秒来了 8 个,是平均值的八倍。而这还只是二十万秒——观察得越久,你见到的最大值就越大,因为泊松分布的尾巴没有硬上界。

把这两个数放在一起,就是这一章的全部意思:「平均每秒 1 个」这句话,描述的是一个 37% 的时间空着、偶尔一秒来 8 个的世界。而你要准备的产能,得应付后者。

A 「空秒应该很罕见」——这是把随机当成了均匀。如果到达真的均匀,那 λ=1 时确实每秒恰好来一个,空秒为零。 B 「大约 10%」——方向对了但低估。有意思的是,P(0 个) = P(1 个) 在 λ=1 时精确成立,两个都是 36.8%。 D 「大约 50%」——高估了。50% 的空秒对应的是 λ ≈ 0.69,也就是平均每秒 0.69 个。

这一章的一句话

真随机是成团的;你觉得「看起来随机」的那种均匀,恰恰是被安排过的痕迹。

下一章把这件事推到一个更让人不适的结论上:你在随机时刻到达时,看到的世界系统性地比真实世界更糟。时刻表说每 10 分钟一班车,你平均要等的不是 5 分钟——如果班次是纯随机的,你要等 10.00 分钟,整整一个平均间隔。而如果班次的 是 4,你要等 25.00 分钟。这不是你运气差。