“这套系统能扛多少流量”很难凭直觉回答。难在两处:容量不是单一数字而是一组相互影响的参数;测算环境与真实环境总有差距,测出来的数不能直接搬用。把这两处理清楚,估算才有意义。
四个基本量与它们的换算
容量讨论里反复出现四个量:QPS、并发数、单次请求耗时、单机承载能力。它们之间有一条来自利特尔法则的核心关系:
并发数 ≈ QPS × 平均耗时
单位必须统一:耗时用毫秒而 QPS 用秒,算出的并发会差一千倍,这是估算里最常见的低级错误。
这条公式正反都能用:单机能同时处理 200 个并发、平均耗时 50ms,则 QPS 上限约 200 / 0.05 = 4000;目标 10000 QPS、平均耗时 50ms,需要的并发能力是 10000 × 0.05 = 500。
单机并发上限从哪来?CPU 密集场景可粗略从核数推:假设 8 核、每请求消耗 5ms CPU 时间,理想上限是 8 / 0.005 = 1600 QPS。这是理想值,上下文切换、垃圾回收、锁竞争都会吃掉一部分。它适合量级判断——”大概千级而不是万级”,不是精确预测。
| 量 | 单位 | 怎么得到 | 常见误区 |
|---|---|---|---|
| QPS | 请求/秒 | 网关或应用埋点统计 | 把峰值当作常态 |
| 并发数 | 个 | 网关活跃连接、线程池活跃数 | 与在线用户数混为一谈 |
| 耗时 | 毫秒 | 服务端处理时间,看分位数 | 只看平均值 |
| 单机承载 | QPS | 压测拐点前的稳定区间 | 用压测最大值当容量 |
木桶效应同样成立:CPU、内存、带宽、磁盘 IOPS、连接池、下游限流,任何一项先到上限,容量就停在那里。估算要逐一问过去,而不是只算 CPU。
压测的四种类型
压测不是”打满看看”,不同类型回答不同问题。
| 类型 | 加压方式 | 回答什么问题 | 停止条件 |
|---|---|---|---|
| 基准测试 | 单接口、固定并发 | 这个接口单机能到多少 | 指标稳定若干分钟 |
| 负载测试 | 从低到高逐级加压 | 性能曲线在哪出现拐点 | 出现明显拐点后再上一级 |
| 压力测试 | 超过预期峰值 | 崩溃点在哪、崩溃后能否恢复 | 错误率超标或不可恢复 |
| 稳定性测试 | 中等压力持续数小时以上 | 有没有泄漏、有没有缓慢劣化 | 达到预定时长 |
基准测试的意义是建立可比的数字:改动前后各跑一次看差异,条件是固定的——同样的并发、时长、数据分布。
负载测试看曲线形状而不是某个点:QPS 随并发上升,起初近似线性,到某点后增长放缓,再往后可能不升反降。拐点之后进入排队状态,延迟上升而吞吐不再增加,容量应取拐点之前。
压力测试观察崩溃形态:渐进劣化还是突然雪崩,撤压后能否自行恢复。稳定性测试暴露慢变量——内存增长、句柄泄漏、日志占满磁盘,两小时的压测看不出来。
压测环境为什么会骗人
压测环境与生产的差异是系统性的:
- 数据量级不同:十万行与十亿行的查询计划不同,索引选择性和缓存命中率被数据量放大或缩小。
- 依赖不同:下游沙箱或 mock 的延迟分布与真实不一致,而延迟分布恰恰决定排队行为。
- 网络拓扑不同:少负载均衡、少一跳、同机房,延迟基数就不同;生产跨可用区的那一跳可能是毫秒级。
- 配置不同:连接池、线程池队列、JVM 参数、CPU 型号,任何一项都会改变拐点位置。
结论是:压测值用于”发现拐点”和”比较版本差异”可靠,用于”直接宣布生产容量”不可靠。可行做法是维护环境差异清单,再结合灰度放量验证——先放 5% 真实流量看指标是否与推断一致,不一致就修正模型。
瓶颈定位的顺序
客户端(压测机)。压测机自身是否打满:CPU、可用端口、文件句柄、客户端 GC。压到上限的表现是”服务端很稳但 QPS 上不去”,判断方法是看压测机资源使用率,或换一台机器发压看结果是否变化。
网络。带宽、PPS、并发连接数;经过 NAT 网关或负载均衡时,连接数上限和空闲超时可能先成为瓶颈,连接复用与否在这里影响很大。
网关。限流规则是否生效、TLS 握手消耗的 CPU、上游连接是否复用。短连接场景下 TLS 握手的 CPU 开销常被低估。
应用。线程池队列积压、GC 停顿、锁竞争、连接池等待、慢依赖。最有效的证据是把队列等待与处理时间拆开——总耗时上升但处理时间不变,说明是排队。
数据库。慢查询、锁等待、IOPS、主从延迟、连接数。它既是瓶颈又是下游,扩容比无状态服务难得多。
为什么压测必须包含”慢”
只压”均匀的正常请求”会得到偏乐观的结论。真实流量里有慢请求:慢 SQL、慢下游、大报文、缓存未命中、冷启动。慢请求的危害不在自身,而在占用资源的时间更长——耗时从 10ms 变成 1s,占用连接和线程的时间是一百倍,排队因此放大。
排队论的直觉值得记住:资源利用率接近 100% 时,排队延迟不是线性增长而是急剧上升。系统看起来”还有余量”,实际已进入不稳定区——这也是容量要取拐点之前的原因。
压测脚本要显式构造慢场景:一定比例的缓存未命中、复杂查询、超大请求体、下游注入固定延迟。观察指标也要从平均耗时换成分位数——P99 和 P999 才是用户体验的真实边界,平均值会被大量快请求掩盖。
扩容为什么不是线性的
加了机器,容量未必按比例增长。非线性来源有几类:
- 连接池:每个新实例都建立自己的下游连接,实例翻倍则连接数翻倍,数据库的连接上限会先到。
- 共享资源:分布式锁、单线程协调组件、消息队列固定分区数、数据库写入能力,不会因为加法扩容而变强。分区数决定消费者的并行上限,扩容超过分区数的消费者是无效的。
- 缓存:新实例是冷缓存,启动时回源压力集中,一批实例同时扩容可能造成回源风暴。
- 下游配额:调用第三方有 QPS 配额,这个数字与实例数无关。
- 长尾依赖:整体吞吐受限于最慢的那个依赖,扩容上游只是让请求更快堆积在它前面。
判断线性是否成立的办法是逐步扩容并观察:实例翻倍而吞吐只增 30%,说明瓶颈在别处,继续加机器只是增加成本。
余量与自动扩缩容阈值
容量要留余量:流量有波动、机器会故障、变更需要时间。常见做法是把目标利用率定在 50%~70%(经验值,取决于波动幅度与恢复速度)。
自动扩缩容的阈值设定有几处容易踩:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler # 示意:字段以集群实际版本为准
spec:
minReplicas: 4
maxReplicas: 40
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 50
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
CPU 指标的局限是对 IO 密集型服务不敏感——请求卡在等下游,CPU 很低但已在排队。这类服务更适合用并发数、队列长度或自定义延迟作为依据。
阈值还要留出反应时间。扩容不是瞬间的:调度、拉镜像、启动、预热、通过就绪探针,加起来是分钟级。阈值若接近崩溃点,等触发时流量早已把系统压垮,应设在”从触发到生效这段时间内不会打穿”的水平。
缩容要更保守:缩得太快会在流量回落又回升时来回抖动,stabilizationWindowSeconds 就是给缩容一个观察窗口;缩容直接减少容量,风险大于扩容。
配置写完之后仍要验证:在压测中实际触发一次扩容,看它是否来得及、扩出来的实例是否真能承载。没验证过的扩缩容配置等同于没有。
评论(0)
还没有评论,来抢沙发。
请 登录 后参与评论。