容量规划、压测与扩容决策实战
容量规划不是“CPU 到 80% 就加机器”,而是把业务目标、用户访问模型、单实例能力、依赖上限和故障冗余转换为可以验证的容量模型。好的容量规划能够回答三个问题:当前系统还能承受多少负载、下一次活动需要准备多少资源、超过设计能力时系统如何有序降级。
本文面向 Web 服务、API、数据库、中间件和容器平台。示例数字用于说明方法,不能直接替代真实压测和生产数据。
一、先区分四类容量
| 容量类型 | 关注内容 | 常见误区 |
|---|---|---|
| 业务容量 | 用户数、订单量、任务量、文件量 | 只看服务器配置,不看业务峰值 |
| 服务容量 | QPS、并发数、吞吐量、响应时间 | 把平均值当峰值 |
| 资源容量 | CPU、内存、磁盘、IOPS、带宽 | 只看利用率,不看排队和延迟 |
| 可靠性容量 | 单节点故障、机房故障后的剩余能力 | 平时刚好够用,故障时立即过载 |
生产容量必须同时满足性能和可靠性。例如 4 个实例在正常情况下刚好承载峰值,但失去 1 个实例后剩余 3 个已经过载,这不是满足高可用要求的容量设计。
二、建立业务负载模型
先从业务量推导请求量,不要直接猜 QPS。假设某系统日活 20 万,10% 的用户集中在最忙的 30 分钟内,每位用户产生 12 次动态请求:
峰值请求量 = 200000 × 10% × 12 = 240000 次
平均峰值 QPS = 240000 ÷ 1800 ≈ 134
真实流量会有秒级突发,还要加入峰值系数。若历史数据表明一分钟峰值通常是半小时均值的 2.2 倍,则目标至少为 295 QPS。活动、批处理和爬虫流量应分开建模,否则平均值会掩盖不同请求的成本差异。
建议记录以下输入:
- 日、小时、分钟和秒级峰值,不只保存日均值;
- 请求类型占比,例如查询、写入、导出和登录;
- 请求大小、返回大小、缓存命中率和数据库访问次数;
- 周期性任务、消息消费和备份窗口;
- 年增长率、活动增长系数与不可预测余量;
- 允许的延迟、错误率和排队长度。
三、用 Little 定律理解并发
在稳定系统中,可以使用近似关系:
并发数 ≈ 吞吐量 × 平均响应时间
当吞吐量为 300 QPS、平均响应时间为 0.2 秒时,平均在途请求约为 60。若下游数据库变慢,响应时间升到 2 秒,即使流量不变,并发也会升到约 600,连接池、线程池和内存压力会同步上升。因此,“并发突然增加”可能是原因,也可能只是下游变慢后的结果。
容量评估应同时观察 QPS、并发和延迟。只提高线程数通常不能解决下游已经饱和的问题,反而可能扩大排队和超时风暴。
四、确定服务级容量目标
先定义通过条件,再开始压测。一个示例目标如下:
| 指标 | 通过条件 |
|---|---|
| 吞吐量 | 持续 20 分钟达到 350 QPS |
| 延迟 | P95 小于 300 ms,P99 小于 800 ms |
| 错误率 | 小于 0.1% |
| CPU | 单实例稳定低于 70% |
| 内存 | 无持续增长和频繁 Full GC |
| 数据库 | 连接池低于 70%,复制延迟在目标内 |
| 恢复 | 压测停止后 5 分钟内队列恢复正常 |
平均响应时间会掩盖长尾请求,容量验收应优先使用 P95、P99。还要定义“错误”:HTTP 200 但返回空结果、降级页或业务失败码,也可能是失败。
五、设计代表性压测
1. 流量组成
按照生产比例编排请求,而不是只压一个最简单接口。登录态、缓存冷热、不同租户、大小对象和读写比例都要覆盖。
2. 压测阶段
- 基线测试:低负载验证脚本、数据和指标是否正确。
- 阶梯测试:逐级增加流量,寻找性能转折点。
- 峰值测试:验证预计业务峰值和安全余量。
- 压力测试:超过设计值,确认系统如何退化。
- 稳定性测试:持续数小时,观察泄漏、GC、队列和连接。
- 恢复测试:撤掉流量后确认积压能否消退。
不要在未批准的生产系统直接进行破坏性压测。涉及支付、短信、邮件、库存和第三方接口时,应使用隔离数据、沙箱或明确的流量阻断措施。
六、寻找真正的饱和点
当负载增加时,系统通常经历线性区、弯折区和崩溃区:
- 线性区:吞吐量随资源增加,延迟变化较小;
- 弯折区:吞吐增长变慢,延迟、队列和超时快速增加;
- 崩溃区:重试放大负载,吞吐反而下降。
常见证据包括:
- CPU 使用率高且运行队列增长;
- CPU 不高,但锁等待、I/O wait 或下游延迟增加;
- 内存接近上限、GC 暂停增长或发生 OOM;
- 磁盘延迟和队列深度上升;
- 数据库活跃连接到顶、锁等待和慢查询增加;
- 消息积压增长速度高于消费速度;
- 出口带宽、NAT 端口或负载均衡连接数接近上限。
“CPU 只有 30%”不能证明还有 70% 容量。单线程热点、锁竞争、限流、连接池和外部配额都可能先成为瓶颈。
七、计算实例数量与冗余
假设单实例在满足 P99 延迟目标时稳定承载 85 QPS,预测峰值为 350 QPS,容量利用目标为 65%:
基础实例数 = ceil(350 ÷ (85 × 65%)) = 7
如果要求任意 1 个实例故障后仍满足峰值,则至少检查 6 个实例是否仍能承载。如果跨可用区部署,还要验证失去一个可用区后的剩余容量,而不是只做 N+1 算术。
资源冗余应按故障域设计:
- 单进程故障:实例级冗余;
- 单主机故障:反亲和或分散调度;
- 单可用区故障:跨区容量;
- 区域故障:跨区域恢复或流量切换;
- 下游限额:配额预留和降级策略。
八、自动扩缩容不等于容量规划
自动扩容有启动时间、镜像拉取、预热、缓存建立和健康检查延迟。若流量在 1 分钟内暴涨,而新实例 8 分钟才能可用,单靠 CPU HPA 无法保护系统。
应组合使用:
- 预测性扩容:活动前提前增加容量;
- 快速指标:请求队列、并发或自定义业务指标;
- 最小实例数:保留即时冗余;
- 扩容速度和稳定窗口:避免抖动;
- 负载保护:限流、排队、负载卸载和熔断;
- 缩容保护:确认长连接、任务和缓存不会被突然终止。
九、容量报告模板
每次评估至少输出以下内容:
业务范围:订单查询 API
预测周期:未来 6 个月
峰值模型:350 QPS,读写比 8:2,峰值系数 2.2
性能目标:P95 < 300 ms,错误率 < 0.1%
单实例能力:85 QPS(满足延迟目标时)
部署建议:7 个实例,跨 3 个故障域
主要瓶颈:数据库写入、NAT 连接上限
保护措施:限流、排队、写入降级、提前扩容
触发条件:预测 14 天内达到安全容量的 75%
回退条件:错误率、P99 或下游延迟超过阈值
报告必须写明测试版本、数据规模、环境差异和不确定性,避免把测试环境结果当成生产保证。
十、扩容决策树
- 用户体验是否接近 SLO 边界?若没有,先确认是否只是资源利用率偏高。
- 是否存在明确的饱和证据?确认 CPU、内存、I/O、锁、连接或配额。
- 能否通过修复低效查询、缓存或批处理降低需求?
- 瓶颈能否水平扩展?有状态组件可能需要分片或架构调整。
- 扩容后下游是否成为新瓶颈?
- 故障域和冗余是否仍满足要求?
- 是否有验证、监控、停止条件和回退方案?
十一、常见错误
- 用平均 QPS 和平均延迟估算峰值容量;
- 只压应用,不准备与生产相近的数据规模;
- 忽略缓存冷启动、连接建立和 JVM 预热;
- 只看压测期间,不看停止后的积压恢复;
- 把资源利用率阈值直接当作业务容量;
- 无限重试,导致过载时形成重试风暴;
- 扩容应用实例,却未检查数据库、DNS、NAT 和第三方配额;
- 没有保留单节点或单故障域失效余量。
十二、容量模型需要持续校准
容量报告不是永久结论。代码版本、数据量、请求结构、缓存命中率、实例规格和下游配额变化后,单实例能力都可能改变。建议建立以下复核触发器:
- 核心版本或架构发生明显变化;
- 数据规模、日活或峰值流量增长超过 20%;
- 实例规格、数据库参数或存储类型调整;
- 出现由过载、队列或限流引发的事故;
- 大型活动和容量采购之前;
- 即使没有变更,也至少按季度复核关键服务。
每次复核应比较预测与实际:预测峰值、实际峰值、计划实例数、实际实例数、单实例吞吐、资源水位和用户 SLI。如果实际偏差长期超过预设范围,应修正模型中的峰值系数或请求成本,而不是每次临时增加余量。
容量数据也要保留版本。报告应能回答“当时为什么买这些资源”,避免只留下最终数字而没有业务假设和测试证据。
十三、上线前检查清单
- [ ] 有分钟和秒级峰值数据,而不是只有日均数据;
- [ ] 请求类型、数据量和缓存状态具有代表性;
- [ ] 定义了吞吐、长尾延迟、错误率和恢复目标;
- [ ] 找到了第一个饱和资源及其证据;
- [ ] 验证了单节点或单故障域失效后的容量;
- [ ] 扩缩容指标、速度、预热与缩容保护已验证;
- [ ] 限流、排队、熔断和降级策略可用;
- [ ] 结果记录了版本、环境差异和有效期;
- [ ] 容量报告有负责人和下次复核日期。