文档目录

0.5 为什么 90% 利用率已经很危险

上一节:0.4 用 Little’s Law 估算容量 | 下一节:0.6 协调遗漏:压测报告的最大谎言 配套代码:00-introduction/05-queueing 动手:这一节强烈建议跑一遍配套模拟,因为结论违反直觉。


一句话结论

延迟对「利用率」的反应不是线性的。 CPU 用到 50% 时,排队几乎不存在;用到 90% 时,平均排队时间已经是服务时间的 9 倍。这就是容量规划必须留 30%–50% 余量的数学依据,而不是「保险起见」。


一、先用超市收银台建立直觉

想象一家超市只有一个收银台(一个 CPU 核 / 一个连接):

收银员的忙碌程度 你去结账时的体验
50% 忙(一半时间在发呆) 你走过去基本不用等,直接结账
80% 忙 有时要等一下,因为前面可能正好有人
90% 忙 大概率要排队,而且队伍不短
95% 忙 几乎每次都要排长队
99% 忙 队伍无限增长,系统实际上已经瘫了

关键洞察:收银员只从 50% 忙到 90% 忙,「空闲时间」只从 50% 降到 10%(少了 5 倍),但你的等待时间却涨了远不止 5 倍。

为什么?因为空闲时间是「吸收波动」的缓冲。当收银员有 50% 空闲时,偶尔来一波客人马上就能消化掉;当只剩 10% 空闲时,一波客人来了就得排队,而排队期间又来了新客人——排队会自我强化。


二、把它量化:排队公式

在「单队列、单服务、到达时间随机、服务时间随机」这个简化模型下(学术上叫 M/M/1),平均排队等待时间的近似公式是:

平均等待时间 ≈ (ρ / (1 - ρ)) × 服务时间

其中 ρ(rho)就是利用率,取值 0 到 1。

代入不同的 ρ:

利用率 ρ 系数 ρ/(1-ρ) 平均排队时间 换句话说
0.5(50%) 1.0 = 1 × 服务时间 排队时间约等于服务本身
0.8(80%) 4.0 = 4 × 服务时间 服务 10 ms → 平均等 40 ms
0.9(90%) 9.0 = 9 × 服务时间 服务 10 ms → 平均等 90 ms
0.95(95%) 19.0 = 19 × 服务时间 服务 10 ms → 平均等 190 ms
0.99(99%) 99.0 = 99 × 服务时间 服务 10 ms → 平均等 990 ms

看清楚这张表:利用率从 90% 涨到 95%,只多了 5 个百分点,但排队时间从 9 倍涨到 19 倍——翻了一倍多。

这就是为什么「我们 CPU 只用了 90%,还有余量」是一句危险的话。那 10% 不是「余量」,是「马上要溢出的缓冲区」。


三、这对工程实践意味着什么

3.1 容量规划必须留余量(headroom)

假设你的服务在 4 核 8G 的机器上,压测发现 CPU 打到 100% 时能处理 1000 QPS。那么这台机器的安全容量是多少?

目标利用率 安全容量 理由
90% 900 QPS 排队已经是服务时间的 9 倍,P99 很难看
80% 800 QPS 排队 4 倍,大多数场景可接受
60%–70% 600–700 QPS 推荐区间:能吸收突发流量与 GC 停顿
50% 500 QPS 很保守,适合延迟极敏感的服务

注意这里的反直觉之处:不是「1000 QPS 打满才算用尽」,而是「跑在 600 QPS 时,你才真正有余力应对突发」。

突发流量怎么来的:重试风暴、定时任务、缓存集中失效、上游重试、爬虫、业务活动。这些都不是你能控制的。

3.2 不要把所有资源都跑满

这条原则适用于所有「排队型」资源:

资源 建议水位 超过后会怎样
CPU < 60%–70% 排队延迟非线性上升、GC/JIT 线程抢不到 CPU
数据库连接池 pending 长期为 0,active < 70% 请求开始排队,延迟雪崩
线程池 / 协程调度器队列 队列深度接近 0 任务堆积,延迟不可控
磁盘 IO 队列 队列深度低 await 时间飙升
网络带宽 < 70% 丢包、重传、TCP 拥塞

3.3 「平均利用率」是骗人的

一个更隐蔽的问题:平均 60% 不代表安全。

假设 CPU 在「30%」和「95%」之间来回跳,平均是 62%。看起来达标,但实际上在那些 95% 的时刻,系统已经在剧烈排队了。

所以要看的是「利用率的分布」,而不是平均值——至少要看 P95/P99 利用率,或者直接看「饱和度指标」(连接池 pending、队列深度)。

顺带一提:这和第 3 节是同一个道理的不同侧面。平均值的谎言,在延迟上表现为「掩盖长尾」,在利用率上表现为「掩盖高峰」。


四、这个模型的边界(不要生搬硬套)

M/M/1 是一个极度简化的模型,现实中有很多偏离:

模型假设 现实 影响
到达时间是随机的(泊松) 真实流量有规律的波峰(早高峰、活动) 实际比模型更差
服务时间随机 有缓存时会呈现「快慢两极」 分布更复杂
单队列单服务 多核、多阶段、有优先级 需要更复杂的模型
队列无限长 真实队列有上限,满了会拒绝 到达上限后是「快速失败」而非「无限等待」

所以这个公式的用途是:

  • ✅ 建立方向性直觉:「利用率高 → 延迟非线性爆炸」这个结论永远成立。
  • ✅ 作为留余量的依据:30%–50% 的 headroom 不是拍脑袋。
  • ❌ 不要用它精确预测你的系统延迟——那要靠实测。

五、本节小结

  1. 延迟对利用率的反应是非线性的:ρ=0.5 → 1×,ρ=0.8 → 4×,ρ=0.9 → 9×,ρ=0.95 → 19×。
  2. 「90% 利用率」不是「还剩 10% 余量」,而是「延迟已经涨了 9 倍,缓冲区即将耗尽」。
  3. 容量规划要留 headroom(常用 30%–50%),这是数学结论,不是保守主义。
  4. 所有排队型资源(CPU、连接池、线程池、IO 队列)都适用这条规律。
  5. 要看利用率的高峰(P95/P99)或饱和度指标,而不是平均值。
  6. M/M/1 是简化模型,用来建立直觉和定余量,不用来精确预测。

六、自测

  1. 同事说:「我们服务器 CPU 才用 65%,很健康。」你会追问哪两个问题?
  2. 一个服务在压测中表现为:QPS 从 500 涨到 700 时,P50 只从 10 ms 涨到 12 ms;但从 700 涨到 800 时,P50 从 12 ms 直接涨到 90 ms。请用本节的模型解释这个现象,并说明这台机器的「实用容量」应该是多少。
  3. 为什么「加缓存」能缓解利用率问题?它改变的是公式里的哪个量?
  1. ① CPU 利用率是平均值还是峰值? 如果是平均值,要看 P95/P99;② 瓶颈是不是 CPU? 如果真正的瓶颈是连接池或下游,CPU 65% 说明不了任何问题——要看饱和度指标(连接池 pending、队列深度)。还可以追问:这是单机还是集群平均、测量时间窗口多长、有没有容器 CPU limit。
  2. 这是典型的拐点现象。500–700 QPS 区间利用率还低(排队系数小),延迟基本等于服务时间;700 QPS 之后利用率逼近高位,排队系数急剧放大,所以 P50 骤增。实用容量应取拐点之前——大约 600–650 QPS(留出余量),而不是 700 或 800。
  3. 缓存降低的是服务时间(原本 20 ms 的数据库查询变成 0.5 ms 的内存读取),从而在同样 QPS 下降低了利用率 ρ。它没有改变到达率 λ,也没有增加服务能力,只是让每个请求「更快做完」,于是同样的硬件能扛更多请求。(副作用:内存成本、一致性风险、以及未命中时的抖动——见 0.2 节的代价清单。)