卷 II · 不齐CH 08深度 8/24

三个因子,相乘

如果这本书只能留下一行字,就是这一行。它把前七章讲的所有东西压成三个相乘的因子——而你会立刻看出,日常那些「让系统变快」的动作,全都在打其中一个,而且往往是最贵的那一个。

★★ 全书骨架Kingman 公式近似在哪里失灵

▷ 先估一个数

你的服务:利用率 80%,到达和服务都是纯随机的(ca² = cs² = 1),平均每次处理 10 毫秒,平均等待 40 毫秒。

老板给你一笔预算,只够做一件事。三个选项,效果分别是:

  1. 换更快的机器:单次处理从 10 毫秒降到 5 毫秒。
  2. 加机器降负载:利用率从 80% 降到 70%。
  3. 治理慢请求:把服务的 cs² 从 1 降到 0。

哪个选项把平均等待压得最低?

A 换更快的机器——单次时间直接砍半,最直接 B 加机器降负载 C 治理慢请求 D 三个效果差不多,选最便宜的那个

那一行

Wq  ≈   ρ/(1−ρ)   ×   (ca² + cs²)/2   ×   E[S]
        ───────────   ─────────────────   ──────
         满的程度        不齐的程度        一次多久

这是 Kingman 公式(1961 年,John Kingman)。三个因子,相乘。

把它们一个一个拆开:

① 满的程度:ρ/(1−ρ)

分母是 1 − ρ,也就是余量。余量在分母上,意味着余量减半,这个因子翻倍还多。

这是三个因子里唯一一个非线性的,也是唯一一个没有上界的。ρ=0.5 时它是 1,ρ=0.9 时它是 9,ρ=0.99 时它是 99。下一卷整整四章都在讲它。

② 不齐的程度:(ca² + cs²)/2

到达的不齐和服务的不齐,各占一半,地位完全平等

这一项是第 5 章那个 的直接应用,而它之所以长这个样子(一个平均),来自第 7 章的检查悖论——你要为「更容易撞上长的那个」买单,来源有两个:到达扎堆,或者服务时长参差。

注意:它也没有上界。而且第 5 章说过,真实系统的 cs² 经常远大于 1。

③ 一次要多久:E[S]

这是唯一一个「速度」项,也是唯一一个线性的。机器快一倍,它减半,就这样。

而且它有个隐藏的连带效应:如果你在不改负载的前提下让机器快一倍,ρ 也会跟着减半,于是因子 ① 也会变小。这是「换更快的机器」真正的价值所在——它同时打两个因子。这个细节在结算里会算清楚。

◆ 怎么用这个公式

不要把它当成一个「算等待时间」的工具——它是近似的,算出来的数别太当真。把它当成一张诊断表。

任何时候有人说「系统变慢了」,你可以问:是哪个因子变大了?

  • ① 变大 → 流量涨了,或者产能掉了。看利用率曲线。
  • ② 变大 → 出现了一批异常慢的请求,或者流量开始扎堆。看延迟直方图的形状,看
  • ③ 变大 → 单次处理真的变慢了。看 p50,看火焰图。

三个因子对应三条完全不同的排查路线。而绝大多数人只会走第三条——因为那是唯一一条有工具的。

它在哪里精确,在哪里不准

Kingman 公式是近似的,而一本教你思考的书有义务告诉你近似在哪里会骗你。好消息是:它失灵的位置很规律。

情形Kingman精确解偏差
M/M/1,ρ=0.8(到达服务都随机)4.00004.0000+0.0%
M/D/1,ρ=0.8(随机来、定时走)2.00002.0000+0.0%
D/M/1,ρ=0.5(定时来、随机走)0.50000.2550+96.1%
D/M/1,ρ=0.82.00001.6927+18.2%
D/M/1,ρ=0.959.50009.1724+3.6%
D/M/1,ρ=0.9949.500049.1678+0.7%

三条规律,都很好记:

  1. 只要到达是泊松的(ca²=1),它就是精确的。此时它退化成 Pollaczek–Khinchine 公式,那是个精确定理,不是近似。而第 6 章说过,大量独立用户叠加就会收敛到泊松——所以在真实的 Web 服务里,这个公式基本上是精确的。
  2. ρ 越接近 1,它越准。D/M/1 在 ρ=0.5 时偏 96%,到 ρ=0.99 时只偏 0.7%。Kingman 本来就是作为重流量极限推导出来的——它在墙脚下最准,而墙脚下恰好是你最关心的地方。
  3. 它偏大,不偏小。在上面所有例子里,Kingman 给出的都是上界。作为一个用来做容量规划的工具,「宁可高估」是正确的方向。

那个 D/M/1 的精确解不是查表来的,是引擎数值解出来的:σ 要满足 σ = e^(−(μ/λ)(1−σ)),ρ=0.8 时 σ = 0.628630,然后 Wq = σ / (μ(1−σ))。第 12 章会拿它做招牌 demo 的一行。

∑ 算一遍:为什么是这三项

公式不是从天上掉下来的。它可以从第 7 章那个「剩余服务时间」推出来:

# 你到达时,要等两样东西:
#   ① 队里已经排着的那些人,全部做完
#   ② 加上正在被服务的那位剩余的时间

Wq = (队里的人数) × E[S]  +  P(有人在被服务) × E[剩余]

# 用 Little 定律把「队里的人数」换成 λ × Wq(第 2 章那条恒等式)
Wq = λ Wq E[S] + ρ × E[S](1 + cs²)/2
Wq = ρ Wq + ρ E[S](1 + cs²)/2         ← 因为 λE[S] = ρ
Wq (1 − ρ) = ρ E[S](1 + cs²)/2
Wq = ρ/(1−ρ) × (1 + cs²)/2 × E[S]     ★ 这就是 P-K 公式(精确)

# Kingman 做的唯一一步近似:把 (1+cs²)/2 换成 (ca²+cs²)/2
# 也就是「到达的不齐,和服务的不齐,同等对待」。
# ca²=1 时两者相同 —— 所以泊松到达下它是精确的。

推导里那一步「用 Little 定律换掉队长」值得停一下:第 2 章那条看起来什么都没说的恒等式,在这里是整个推导的关键。这是它作为「不需要假设」的定律的价值——它可以插进任何一个推导里。

✎ 术语正名:肯德尔记号

上面表格里那些 M/M/1M/D/1 是排队论的标准写法,叫肯德尔记号。三段,用斜杠隔开:

A / B / c
│   │   └── 服务台数量
│   └────── 服务时间的分布
└────────── 到达间隔的分布

# 字母表:
M  = Markovian / Memoryless,指数分布,c² = 1(M 是「无记忆」的意思)
D  = Deterministic,定时,c² = 0
G  = General,任意分布(只知道均值和 c²)
Ek = Erlang-k,c² = 1/k
H2 = 两相超指数,c² > 1

# 所以:
M/M/1   随机来、随机走、一个台子     ← 教科书的默认款
M/D/1   随机来、定时走、一个台子     ← 固定长度的包
D/M/1   定时来、随机走、一个台子     ← 定时任务打到一个变长的处理器上
M/G/c   随机来、任意分布、c 个台子   ← 最接近真实服务的模型

这套记号 1953 年由 David Kendall 提出。它有用的地方在于:它逼你显式回答「到达齐不齐」和「服务齐不齐」这两个问题,而这正是大多数性能讨论里被跳过的两个问题。

▸ 在现实里

把它当成一次事故复盘的框架。下次线上变慢,别直接跳进火焰图。先在白板上写三个因子,逐个问:利用率从多少变成了多少?延迟直方图的形状变了吗?p50 变了吗?这三个问题能在五分钟内把排查范围缩小到三分之一。

为什么「灰度发布变慢了」经常不是新代码的锅。灰度期间,一部分流量走新版本、一部分走旧版本,两个池子各自的规模都变小了——而池子变小会让同样的利用率产生更长的队(第 14 章)。因子 ① 变大了,而代码一行没动。

为什么加缓存有时候让 p99 更糟。加缓存降低了平均处理时间(因子 ③ 变小),但把请求分成了「命中,1 毫秒」和「未命中,100 毫秒」两群——因子 ② 暴涨。两个因子方向相反,净效果可能是负的。这在真实系统里屡见不鲜,而只看平均延迟的监控完全看不出来。

✗ 这个直觉是错的
三个因子里,最该优化的当然是「一次要多久」——那是我唯一能通过写代码控制的东西。 「一次要多久」是唯一一个线性的因子,也就是收益最有限的那个。快一倍,等待减半,仅此而已。而另外两个因子可以带来一个数量级的变化。

更要命的是,优化单次耗时通常是最贵的——它意味着重写代码、换技术栈、买更好的硬件,工程量以人月计。而:

  • 降低 ρ(因子 ①)通常只要加机器,是花钱就能买到的,而且第 10 章会证明在高利用率下便宜得离谱;
  • 降低 (因子 ②)通常是找出那一小撮慢请求,是局部改动,而且第 5 章算过收益是平方级的。

这一章真正想让你带走的不是公式,是这个排序先看满不满,再看齐不齐,最后才动速度。大多数团队的顺序恰好是反的。

◇ 结算

C治理慢请求。把这三个选项都代进公式:

# 原始:ρ=0.8, (ca²+cs²)/2 = 1, E[S] = 10ms
Wq = 4.0 × 1.0 × 10 = 40 ms

# ① 换更快的机器:E[S] 10→5ms。注意 ρ 也会跟着 0.8→0.4
Wq = (0.4/0.6) × 1.0 × 5 = 0.667 × 5 = 3.33 ms      ← 降到 8.3%

# ② 降负载:ρ 0.8→0.7
Wq = (0.7/0.3) × 1.0 × 10 = 2.333 × 10 = 23.3 ms    ← 降到 58%

# ③ 治理慢请求:cs² 1→0,所以 (1+0)/2 = 0.5
Wq = 4.0 × 0.5 × 10 = 20 ms                          ← 降到 50%

咦?① 才是最好的?

是的——但请注意为什么:因为「机器快一倍」在负载不变时同时打了两个因子(E[S] 减半,ρ 也减半)。它赢不是因为速度重要,而是因为它偷偷降了利用率

如果把这个偷来的好处还回去(比如你换了更快的机器之后立刻把流量也加倍,利用率仍然是 80%——真实世界里这几乎必然发生),那么:

# ①' 换更快的机器,但流量也跟着涨,ρ 保持 0.8
Wq = 4.0 × 1.0 × 5 = 20 ms                           ← 和 ③ 完全一样

所以答案是 C:在控制利用率不变的对比下,治理慢请求和把机器换快一倍效果相同——而后者要花的钱多一个数量级。

这道题真正想教的是:算收益的时候,一定要说清楚「其他条件是否不变」。「换更快的机器」这个动作的收益里,大部分来自它顺手降低的利用率,而那部分收益你本来可以直接花更少的钱买到。

A 「最直接」——它在这道题的字面设定下确实最优,但赢的原因不是速度,是它连带降了 ρ。而这个连带效应在生产环境里通常活不过一个季度。 B 「加机器降负载」——80% 降到 70% 只让因子 ① 从 4.0 降到 2.33。在 80% 这个位置上,10 个百分点还买不到「减半」——第 10 章会给出精确的价目表。 D 「差不多,选便宜的」——从结果上看 ③ 和 ①' 确实差不多,但 ② 明显差一档。而且三者的成本差一个数量级,「选便宜的」在这里恰好会选中最优解。

这一章的一句话

等待 = 满 × 不齐 × 一次多久;三个因子里只有一个是速度,而它是唯一线性的那个。

卷 II 结束。你现在有了公式的完整形状。下一卷只做一件事:把第一个因子 ρ/(1−ρ) 画出来,反复地看。它是一条你在中学见过、但从来没在工作里认出来的曲线——从 50% 到 90%,等待涨 9.0 倍;从 90% 再到 95%,只多用了 5 个百分点,等待再涨 2.1 倍