卷 V · 插队CH 19深度 19/24

优先级不生产时间,它只搬账单

「把这个接口设成高优先级」是一个听起来免费的动作。这一章给出它的价签,而且是精确到小数点后六位的价签——因为有一条守恒律在管着它。

★★ 守恒律谁付账优先级通胀

▷ 先估一个数

一条队,总利用率 80%,不分优先级时平均等待 4.0000 个服务时长。

现在你把流量分成两类:高优先级占 37.5%(利用率 0.30),低优先级占 62.5%(利用率 0.50)。高优先级永远插在低优先级前面(但不打断正在做的)。

高优先级的平均等待会降到 1.1429。那么低优先级的平均等待会涨到多少?

A 大约 4.5——只涨一点,因为高优先级只占三成多 B 大约 5.7 C 大约 8——高优先级省下的,低优先级全部承担 D 涨到无穷——低优先级会被饿死

一个纹丝不动的数

拖动上面那根滑杆。两根柱子一高一低地动,而统计格里那个 3.200000 一动不动。

高优先级占比高优先级等待低优先级等待Σ ρₖ×Wq,k
10.0%0.86964.34783.200000
37.5%1.14295.71433.200000
70.0%1.81829.09093.200000

这就是克莱因洛克守恒律(Kleinrock's conservation law,1965):

Σ ρₖ × Wq,k  =  ρ × Wq(先来后到)   =  常数

# 也就是说:
# 把每一类的"等待时间"按它的"利用率份额"加权求和,
# 这个和与你怎么排优先级**完全无关**。

# 这一章的例子:
高优先级  ρ=0.300  Wq=1.1429   贡献 0.342857
低优先级  ρ=0.500  Wq=5.7143   贡献 2.857143
─────────────────────────────────────────
加起来                          3.200000
不分优先级 ρ×Wq = 0.8 × 4.0 =   3.200000    ← 一分不差

这不是数值巧合。这是一条定理,而且它是第 17 章那个「未完成工作量守恒」的直接推论:V(t) 不变,那么按份额加权的等待就不变。

◆ 优先级的真实价签

守恒律给了「设优先级」这个动作一个精确的价签:

高优先级那一类省下的时间,乘以它的利用率份额,精确地等于低优先级那一类多等的时间,乘以它的份额。

验算一下:

高优先级省下:(4.0000 − 1.1429) × 0.300 = 0.857143
低优先级多付:(5.7143 − 4.0000) × 0.500 = 0.857143    ★ 精确相等

所以「把这个接口设成高优先级」不是一个技术决策,是一个分配决策。它的正确表述是:「我决定让 B 类请求多等 43%,来换 A 类请求少等 71%。」

这句话说出来,讨论的性质就变了——它变成了一个可以被质疑、需要理由的选择,而不是一个免费的优化。

优先级通胀

守恒律有一个立刻可以推出的推论,而它解释了每个大型组织里都会发生的一件事。

如果所有人都把自己的服务设成高优先级会怎样?

看上面那张表的第三行:高优先级占到 70% 时,它自己的等待涨到了 1.8182——比占 10% 时的 0.8696 差了一倍多。

推到极限:如果所有流量都是高优先级,那么它等价于没有优先级,所有人回到 4.0000。

# 优先级的价值 ∝ (1 − 你上面那些人的利用率份额)
Wq,k = W₀ ÷ ( (1 − σ_{k−1}) × (1 − σ_k) )

  σ_{k−1} = 比你优先级高的那些类,利用率加起来是多少
  σ_k     = 加上你自己

# 高优先级那一类占的份额越小,它享受到的加速越大。
# ★ 优先级是一种**稀缺资源**,稀缺性来自"用它的人少"。

这就是优先级通胀:一旦「设成高优先级」是免费的,每个团队都会去设,然后这个标记就贬值了。任何在大公司待过的人都见过这个过程——工单系统里所有工单都是 P0,告警里所有告警都是「紧急」。

治理办法只有一个:让优先级有成本。可以是配额(每个团队只能有 X% 的流量是高优先级)、可以是预算(高优先级的流量收费更贵)、也可以是审批。但它必须有成本,因为它本来就是有成本的——守恒律说了,那个成本一分不少地落在别人身上。

∑ 算一遍:那个 W₀ 是什么

公式里那个 W₀ 有一个具体的物理含义,而且是老朋友:

W₀ = Σ λᵢ E[Sᵢ²] ÷ 2

# 它是"你到达的那一刻,正在被服务的那位还剩多久"的期望。
# 也就是第 7 章的**检查悖论**。

# 这一章的例子(两类都是指数服务,E[S]=1,E[S²]=2):
W₀ = (0.3 × 2 + 0.5 × 2) ÷ 2 = 0.8

# 然后
高优先级:Wq = 0.8 ÷ ((1 − 0) × (1 − 0.3))     = 1.1429
低优先级:Wq = 0.8 ÷ ((1 − 0.3) × (1 − 0.8))   = 5.7143
不分优先:Wq = 0.8 ÷ (1 − 0.8)                  = 4.0000

注意最高优先级那一类的分母:(1 − 0) × (1 − 0.3)。第一项是 1,因为它上面没有人。但它仍然要等 1.1429,不是 0——那 1.1429 主要就是 W₀ 除以一个接近 1 的数,也就是「等正在做的那位做完」。

这是非抢占优先级的天花板:就算你是最高优先级,你也得等当前那个活做完。想突破这个天花板,只能允许抢占——而抢占在很多场景里做不到(一个正在写盘的事务没法打断)。

推论:如果你的 cs² 很大(有巨无霸请求),那么优先级的效果会被 W₀ 拖累——因为最高优先级也要等那个巨无霸。这时候更有效的做法是第 18 章的按大小分流,或者干脆把巨无霸拆小。

✎ 术语正名:「服务质量」(QoS)

网络设备和云平台上的「QoS 配置」常常被理解成「给重要流量开小灶,让它更快」。

守恒律说:QoS 不生产带宽,它只决定拥塞时谁先被牺牲。

这有一个非常实际的推论:在不拥塞的时候,QoS 配置完全没有作用。如果你的链路利用率是 30%,那么不管怎么标记 DSCP、怎么配队列,所有包的延迟都一样——因为根本没有队要排。

所以「配了 QoS 但没效果」通常不是配错了,是你的系统还没到需要它的时候。而反过来,「配了 QoS 之后某些业务变慢了」也不是 bug,那是守恒律在收账。

▸ 在现实里

急诊分诊。医院的分诊系统(triage)是优先级队列最成熟的实现,而且它诚实地承认了守恒律:分诊等级明确告诉轻症患者「你会等更久」。它甚至有一个这本书没讲的机制——动态优先级:等得越久等级越高,防止饿死。这就是第 18 章提过的「老化」。

为什么「所有请求都很重要」是一个昂贵的立场。如果你拒绝给流量分级,那么在过载时系统会随机地牺牲一部分请求——而随机的那部分里一定包含你最在乎的。不做优先级不等于公平,等于把分配权交给运气。

Kubernetes 的 PriorityClass 和 QoS Class。这套机制在资源不足时决定驱逐谁。它的设计文档里有一句很诚实的话:优先级不保证你的 Pod 跑得更快,只保证在需要杀 Pod 的时候先杀别人。这正是守恒律的表述。

数据库的锁等待。很多数据库支持给事务设优先级。但要注意:如果高优先级事务持有的锁被低优先级事务等着,那么「让高优先级先走」反而可能让整体更慢(优先级反转)。守恒律成立的前提是任务之间独立,一旦有依赖,事情会更复杂。

✗ 这个直觉是错的
给关键接口加上高优先级,是一个纯收益的优化——反正低优先级的请求本来也没人在意。 守恒律说,你从高优先级那里省下的每一秒,都精确地转移给了低优先级。如果你真的不在意低优先级,那你应该直接把它们丢掉——那才是真正的净收益。

这里有一个很重要的区分,它是这一卷和下一卷的桥梁:

  • 调整优先级:在队里换顺序。总账不变(守恒律)。
  • 丢弃请求:把活从队里拿走。总账真的变小了——因为 V(t) 变小了。

第 21 章会专门讲第二件事。而这一章要说清楚的是:只要你还在「排序」的框架里,你就在零和游戏里。真正的减负必须来自减少工作量、增加产能、或者压低

还有一个实践上的陷阱:低优先级流量往往不是「不重要的用户」,而是「重要用户的不重要请求」。把批量导出设成低优先级,结果某个大客户导出一份报表等了四十分钟,然后打电话来投诉。守恒律不关心你的分类是否明智,它只负责把账算清楚。

◇ 结算

B5.7143。从 4.0000 涨到 5.7143,多等 42.9%

验算守恒律:0.300 × 1.1429 + 0.500 × 5.7143 = 3.200000,而 0.8 × 4.0000 = 3.200000。一分不差。

A 「只涨一点」——低估了。原因是这个直觉在做线性分摊:「高优先级占三成多,那么代价也分摊掉三成多」。但守恒律是按利用率份额加权的,而且被 (1−σ) 那个分母放大——低优先级的分母里有 (1−0.8),那是余量,很小。 C 「高优先级省下的全部由低优先级承担」——方向完全正确,这就是守恒律。但要注意是加权转移:高优先级省了 2.8571,乘以份额 0.3 得 0.857;低优先级要承担同样的 0.857,除以它的份额 0.5,得到多等 1.714。因为低优先级份额更大,它每个人分摊到的反而更少。 D 「饿死」——非抢占优先级不会饿死低优先级,只要总利用率小于 1。饿死发生在两种情况:ρ ≥ 1(第 21 章),或者高优先级的到达是无穷密的。这个选项的直觉来自 SRPT(第 18 章),那里确实有饿死风险。

这一章的一句话

Σ ρₖ×Wq,k 是一个常数;优先级不创造时间,它只决定这笔总账由谁来付。

下一章是卷 V 的最后一章,讲一个每个人都在用、但几乎没人算过账的手段:批处理。你会看到一条 U 形曲线——批大小从 1 改到 2,端到端延迟从 277.75 毫秒掉到 12.30 毫秒1/22.6);再从 2 加到 16,又涨回 55.78 毫秒。而这两段的原因完全不同。