3135 字
约 10 分钟
17
S15-A21 · 吞吐与容量的混淆:能跑多快与能扛多少

压测报告写着 QPS 八千,结论是"容量充足"。上线后两千就挂,且不是因为 CPU 满——内存把容器推到了 limit,连接数撞到了下游上限。报告没错,被读错了:它测的是吞吐,被当成了容量。

吞吐是单位时间能处理多少,容量是同时能容纳多少。两者的瓶颈资源常常不是同一个,所以一个达标的数字不能保证另一个。这篇把两个词分开,给出压测数字生效的三种前提与容量表该填什么。


1. 两个词,两套瓶颈

吞吐(Throughput) 容量(Capacity)
定义 单位时间处理完成的请求量 同时能容纳的在途量
典型瓶颈 CPU、序列化、加解密、下游 RT 内存、连接数、文件句柄、队列长度
时间尺度 短时可达的峰值 长时间运行后的稳定上限
失效表现 响应变慢、排队变长 OOM、连接拒绝、句柄耗尽
扩容能否解决 通常能线性提升 只在无状态部分能

关键差别在最后一行:扩容能提升吞吐,但不一定提升容量。有状态的部分(会话、本地缓存、队列、未完成任务)不随实例线性扩展,实例数翻倍时这部分的总容量可能一点没变,只是被切成了更多的碎片。

用小定律能把两者连起来,这也是它们被混淆的根源:

Little's Law(适用于稳定状态):
  未完成请求数 L = 到达率 λ × 平均耗时 W
─── 吞吐 8000 QPS、平均 RT 200ms → 同时在途 L = 8000 × 0.2 = 1600 个请求

推论一:吞吐上去了、RT 也上去了 → 在途量按乘积增长,容量需求被双重放大
推论二:容量(在途上限)不动而吞吐想提高 → 只能压 RT,压不动就撞墙
─── 但须先确认系统处于稳定状态;排队严重时这条式子会低估内存占用

推论一是最容易被忽略的地方:压测时把 QPS 打上去,同时平均 RT 从 50ms 涨到 200ms,在途量实际涨了 16 倍。容器内存炸掉的时刻,往往不是 QPS 最高的时刻,而是 QPS 中等偏上、RT 已经劣化的那一段。

这个式子在容量评审里有一个非常实际的用法:它把"QPS 目标"翻译成"内存预算"。如果服务每个在途请求占用若干 KB 的上下文,用目标 λ 与可接受 W 算出 L,就能得出必须预留多少内存,而不是等 OOM 之后再加 limit:

// 容量反推的写法,不是精确预测,而是给内存预算一个可解释的来源
目标 λ = 3000 QPS
可接受 W = 0.150 s(P95 而非均值,均值会低估长尾的在途量)
→ L ≈ 450 个在途请求
每个在途请求的上下文约 R KB(含缓冲、序列化中间态)
→ 内存预算 ≈ 450 × R,再乘 1.5 留余量
─── R 必须实测(单请求内存增量),不要用经验常数

2. 压测数字生效的三个前提

任何"QPS 八千"都必须附带三个前提,缺一个就无法移植到生产:

前提 具体指什么 缺了会怎样
数据量 表行数、索引形态、缓存命中率 在空库或小表上测出的数字,生产上指数级失真
请求分布 读写比、参数分布、冷热比例 全读、全命中、单 key 的测试最容易出漂亮数字
持续时间 跑了多久 短测掩盖增长类问题,见下一节

第三条要用不同的时长测同一个配置,结果会不一样:

// 同一个并发,三档时长,看指标是否收敛
wrk -t8 -c200 -d60s   --latency http://[TARGET]/api/orders   // 60 秒:只看吞吐上界
wrk -t8 -c200 -d600s  --latency http://[TARGET]/api/orders   // 10 分钟:看稳态是否漂移
wrk -t8 -c200 -d3600s --latency http://[TARGET]/api/orders   // 1 小时:看内存与连接是否单调上升

─── 判据:60 秒的数字用于定吞吐上界,1 小时的数字才能进容量表
─── 若三档 P99 差异超过一个量级,说明 60 秒的结论不可用

短时压测会掩盖三件事,它们都不在 CPU 曲线上:

1) 内存增长:堆外的、缓存的、连接池的,只在持续运行后显现
2) 连接泄漏:每请求建连未归还,短测时不明显,长测时连接数单调上升到拒绝
3) 缓存被填满后的表现:命中率从 95% 掉到 70% 之后,下游 RT 与错误率同时变化
─── 这三项都表现为"短测都没问题,上线几小时后开始变慢"

3. 峰值与持续、并发与吞吐:填容量表该用哪个数

指标 用途 不能用于
峰值 QPS 定突发能力、定限流阈值 填容量表(短暂可达,不代表可持续)
持续 QPS 定实例数、定数据库规格 定限流阈值(会误伤正常突发)
拐点 QPS 定安全水位 定预算(它随数据量与分布变化)

并发数与吞吐不是线性关系。它在低并发区近似线性,过拐点后吞吐下降——因为排队、上下文切换、GC 压力开始互相放大:

并发扫描的读法(典型形状,具体数值必须实测):
  并发 50  → QPS 3000,P99 40ms
  并发 200 → QPS 7000,P99 90ms      // 拐点在此附近,吞吐最高
  并发 400 → QPS 6800,P99 300ms     // 吞吐不再涨,延迟继续劣化
  并发 800 → QPS 5200,P99 1200ms    // 吞吐回落,开始出现超时类错误
─── 安全容量取拐点 QPS 的 60–70%,并留出下游 RT 劣化的余量

峰值与持续这两个数的正确用法是分开的:限流阈值参考峰值,扩容与规格参考持续。用峰值填容量表,结果是为一年里两次的活动付全年的钱;用持续定限流,结果是大促当天所有请求都被拦在门口。

为什么一过拐点延迟会突然变陡,而不是缓慢变差?近似排队的公式给了答案:

W ≈ W0 / (1 - ρ)        // ρ = 资源利用率,W0 = 无竞争时的服务时间
ρ = 0.5 → W ≈ 2.0 × W0
ρ = 0.8 → W ≈ 5.0 × W0
ρ = 0.9 → W ≈ 10  × W0
─── 利用率没有"线性"区间;瓶颈资源的 80% 与 90% 差一倍,且尾部更长

这条式子的实践后果是:安全水位不该按吞吐定,该按瓶颈资源的利用率定。CPU 长期 85% 的系统,即使吞吐数字漂亮、没有报错,也已经在延迟的陡坡上;此时增加的流量带来的不是排队,是超时。

容量表还要有状态量一列,它决定长时间运行是否稳定:

状态量 上限由什么决定 满溢后的表现
会话/登录态 内存或集中存储的条目数 淘汰导致用户被踢,或内存 OOM
连接(含下游) 本端上限、下游 max_connections 获取连接超时,表现为全线变慢
队列长度 配置上限、内存 拒绝入队或内存持续增长
未完成任务 线程池 + 队列 堆积到超时,任务被丢弃或重复执行

4. 扩容的前提、度量的读法与排错

"我们扩容了,所以能扛"这句话省略了三个前提:共享资源不随实例扩容、有状态部分不随实例扩容、每实例的容量在扩容后仍然成立。数据库连接数是最常见的反例——实例从 4 个扩到 16 个,每个实例的连接池上限不变,数据库连接总数翻了 4 倍,直接撞上 max_connections。

// 扩容前的检查项,缺一项扩容就可能变成故障放大器
shared_resource:
  - db_max_connections: 当前值 / 扩容后需要   // 必须先算乘数
  - cache_qps: 当前值 / 扩容后需要
  - bandwidth: 出向带宽是否成为新瓶颈
stateless_ratio: 该服务的无状态比例           // 会话在本地内存里,扩了也没用
per_instance_capacity: 单实例拐点 × 0.65      // 每实例都用同一个数,不按平均值
度量 怎么取 读法
拐点 QPS 阶梯加压,记录吞吐开始回落的那一档 安全水位 = 拐点 × 0.6–0.7
长稳压测的资源增长斜率 1 小时压测中,内存与连接数按分钟采样 斜率不收敛到 0 → 有泄漏或缓存无上限
连接与队列水位 连接池活跃数、队列深度 长期贴着上限 → 容量不足而非吞吐不足
每实例承载会话数 会话存储条目数 / 实例数 会话集中在单实例 → 扩容无效
现象 常见原因 处理方向
压测八千、上线两千就挂 缺少数据量或分布前提 用生产规模数据 + 生产读写比重测
CPU 不高但请求超时 容量类瓶颈:连接、句柄、队列 看连接水位与队列深度,不看 CPU
运行几小时后开始变慢 内存线性增长或连接泄漏 做 1 小时长稳,看斜率
扩容后总吞吐未提升 共享资源已饱和,或有状态部分在本地 先算共享资源乘数,再谈加实例
长稳压测通过、生产仍抖动 压测环境网络拓扑与下游限流不同 在准生产拓扑下复测,含下游限流

压测环境与生产有三处必然差异,必须在结论里显式标注:数据量(通常是生产的一个零头)、网络拓扑(少一跳或少一个代理)、下游限流(压测时常常被关掉)。在关掉下游限流的压测里得到的吞吐,是"没有下游"时的吞吐。


5. 边界:什么时候问题退化成成本

情况 结论 理由
完全无状态、请求极轻 容量问题变成本问题 加实例即可,只需算钱
可无限横向扩展且有弹性伸缩 关注成本与冷启动 容量由调度器兜住
离线批处理 换成"多久跑完"的问题 没有在途量概念,只有时长
有严格长连接(WebSocket、推送) 容量问题集中在连接数与内存 扩容时要考虑连接迁移的代价
单实例、低流量内部系统 只需要一条资源告警 谈不上容量规划

反过来最需要这套区分的是有共享下游 + 会话有状态 + 有峰值的系统:三个特征同时存在时,吞吐数字与容量结论几乎必然分叉。


6. 自测

  1. 吞吐与容量的瓶颈资源通常分别是什么?
  2. 为什么说"扩容能提升吞吐,但不一定提升容量"?
  3. 用小定律推:QPS 上升且 RT 劣化时,在途量会怎么变?
  4. 压测数字要生效,必须附带哪三个前提?
  5. 限流阈值与容量表分别该参考峰值还是持续 QPS?
  6. 压测环境与生产的三处必然差异是什么?
S15-A21 · 吞吐与容量的混淆:能跑多快与能扛多少
https://www.clxhxhhr.top/posts/6387/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。