优先级不生产时间,它只搬账单
「把这个接口设成高优先级」是一个听起来免费的动作。这一章给出它的价签,而且是精确到小数点后六位的价签——因为有一条守恒律在管着它。
一条队,总利用率 80%,不分优先级时平均等待 4.0000 个服务时长。
现在你把流量分成两类:高优先级占 37.5%(利用率 0.30),低优先级占 62.5%(利用率 0.50)。高优先级永远插在低优先级前面(但不打断正在做的)。
高优先级的平均等待会降到 1.1429。那么低优先级的平均等待会涨到多少?
一个纹丝不动的数
拖动上面那根滑杆。两根柱子一高一低地动,而统计格里那个 3.200000 一动不动。
| 高优先级占比 | 高优先级等待 | 低优先级等待 | Σ ρₖ×Wq,k |
|---|---|---|---|
| 10.0% | 0.8696 | 4.3478 | 3.200000 |
| 37.5% | 1.1429 | 5.7143 | 3.200000 |
| 70.0% | 1.8182 | 9.0909 | 3.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₀ = Σ λᵢ 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 配置完全没有作用。如果你的链路利用率是 30%,那么不管怎么标记 DSCP、怎么配队列,所有包的延迟都一样——因为根本没有队要排。
所以「配了 QoS 但没效果」通常不是配错了,是你的系统还没到需要它的时候。而反过来,「配了 QoS 之后某些业务变慢了」也不是 bug,那是守恒律在收账。
急诊分诊。医院的分诊系统(triage)是优先级队列最成熟的实现,而且它诚实地承认了守恒律:分诊等级明确告诉轻症患者「你会等更久」。它甚至有一个这本书没讲的机制——动态优先级:等得越久等级越高,防止饿死。这就是第 18 章提过的「老化」。
为什么「所有请求都很重要」是一个昂贵的立场。如果你拒绝给流量分级,那么在过载时系统会随机地牺牲一部分请求——而随机的那部分里一定包含你最在乎的。不做优先级不等于公平,等于把分配权交给运气。
Kubernetes 的 PriorityClass 和 QoS Class。这套机制在资源不足时决定驱逐谁。它的设计文档里有一句很诚实的话:优先级不保证你的 Pod 跑得更快,只保证在需要杀 Pod 的时候先杀别人。这正是守恒律的表述。
数据库的锁等待。很多数据库支持给事务设优先级。但要注意:如果高优先级事务持有的锁被低优先级事务等着,那么「让高优先级先走」反而可能让整体更慢(优先级反转)。守恒律成立的前提是任务之间独立,一旦有依赖,事情会更复杂。
这里有一个很重要的区分,它是这一卷和下一卷的桥梁:
- 调整优先级:在队里换顺序。总账不变(守恒律)。
- 丢弃请求:把活从队里拿走。总账真的变小了——因为
V(t)变小了。
第 21 章会专门讲第二件事。而这一章要说清楚的是:只要你还在「排序」的框架里,你就在零和游戏里。真正的减负必须来自减少工作量、增加产能、或者压低 c²。
还有一个实践上的陷阱:低优先级流量往往不是「不重要的用户」,而是「重要用户的不重要请求」。把批量导出设成低优先级,结果某个大客户导出一份报表等了四十分钟,然后打电话来投诉。守恒律不关心你的分类是否明智,它只负责把账算清楚。
B5.7143。从 4.0000 涨到 5.7143,多等 42.9%。
验算守恒律:0.300 × 1.1429 + 0.500 × 5.7143 = 3.200000,而 0.8 × 4.0000 = 3.200000。一分不差。
(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 毫秒。而这两段的原因完全不同。