容量规划把需求预测转化成一个有依据的运行范围。真正有用的问题,不是机器在崩溃之前还能接收多少请求,而是服务能以满足延迟和正确性要求的方式持续处理多少工作。答案受到请求成本、共享依赖和故障假设影响。只有把这些假设的来源、适用范围与负责人写清楚,表格里的实例数量才能支持采购与可靠性决策,而不只是把直觉包装成一个数字。

测出能够接受的运行点

压测应使用具有代表性的请求组合、数据规模、缓存状态、并发方式和软件配置。在增加压力时,同时记录延迟、错误、处理器、内存、队列、存储与连接使用情况。满足要求的最高已测试点,是针对该工作负载的证据,并不证明所有中间点或下一版本都表现相同。只有吞吐量而没有结果质量的测试,甚至可能把快速拒绝请求的服务器评成性能优秀。

不同瓶颈需要分别统计。增加应用副本可能带来更多计算资源,却没有扩大共享数据库的连接额度。请求变贵时,每秒请求数与资源消耗的关系也会改变。因此,按请求速率规划只在成本分布可比时才有意义,还需要监测底层资源,并在工作负载变化后修订模型。不能拿安静时段的平均负载,直接推导高峰时的服务承诺,也不能假设所有资源会同时到达上限。

预测需求,并主动减掉会失去的容量

从实际峰值出发,明确准备应对的是增长、季节波动还是某次活动,再把采购或扩容提前期和预测误差写入计划。运行余量应该对应具体原因,例如负载不均、需求低估或新增实例需要时间,而不是沿用旧表格里来源不明的百分比。出现有意义的版本、数据量或流量结构变化后,应同时复查单机测量和需求假设,避免只更新其中一个数字。

接着按可靠性设计减去可能失效的容量。服务分布在三个可用区,如果要求失去一个区后仍能承接同样峰值,所需实例数就不同于只考虑健康集群的方案。还要核对共享依赖是否幸存、流量如何迁移,以及新增实例能否在队列失控前就绪。均匀部署不等于请求必然均匀分配,自动扩缩容也无法瞬间提供资源,这些都应进入故障演练与容量边界。

坏掉的样子

常见错误是用预测流量除以单机最大吞吐量,向上取整后便结束计算。这样的结果可能通过健康状态检查,却在损失一个可用区后立刻超载。另一个问题是重复计算预留容量:必须区分压测已经验证的速率,与规划时主动采用的更低运行速率。汇报剩余容量时也应说明相对于哪个边界,避免把规划额度内的余量误称为硬件物理吞吐上限。

线性扩展可能在共享锁、数据库、网络链路或外部配额处失效。冷缓存和同步重试还可能使故障切换后的请求比原压测更昂贵。应在真实约束下验证失效场景,并记录模型何时不再成立。如果现有资源无法安全承接预测需求,可能需要经过验证的准入控制或降级方式,等待资源到位;在表格里加入乐观假设,不能改变实际排队和超时行为。

快速验证

保存为 capacity.py 后,用 Python 3 执行。输入是固定工作负载下的人造压测记录,实验用于核对实例计算与故障假设,不代表当前机器的真实性能。示例把可接受测试点的百分之七十作为规划速率,这是为本例明确选择的余量规则,不是适用于所有系统的利用率建议,实际团队应解释自己的预留原因。

from math import ceil

# Synthetic load-test results for one fixed request mix and machine size.
samples = [
    {"rps": 150, "p99_seconds": 0.11, "error_ratio": 0.0001},
    {"rps": 200, "p99_seconds": 0.22, "error_ratio": 0.0003},
    {"rps": 250, "p99_seconds": 0.50, "error_ratio": 0.0020},
]
acceptable = [s["rps"] for s in samples
              if s["p99_seconds"] <= 0.30 and s["error_ratio"] <= 0.001]
tested_rps = max(acceptable)
forecast_rps = ceil(1600 * 1.25)
zones = 3
surviving_zones = zones - 1
planning_rps = tested_rps * 0.70

naive_per_zone = ceil(ceil(forecast_rps / tested_rps) / zones)
planned_per_zone = ceil(forecast_rps / (planning_rps * surviving_zones))
naive_survivor_rps = naive_per_zone * surviving_zones * tested_rps
planned_survivor_rps = planned_per_zone * surviving_zones * planning_rps

assert tested_rps == 200 and forecast_rps == 2000
assert naive_per_zone * zones == 12 and naive_survivor_rps == 1600
assert planned_per_zone * zones == 24 and planned_survivor_rps == 2240
assert naive_survivor_rps < forecast_rps <= planned_survivor_rps
print(f"tested={tested_rps} rps/node; forecast={forecast_rps} rps")
print(f"naive={naive_per_zone * zones} nodes; zone-loss capacity={naive_survivor_rps} rps")
print(f"planned={planned_per_zone * zones} nodes; zone-loss planning capacity={planned_survivor_rps:.0f} rps")
print(f"zone-loss spare={planned_survivor_rps - forecast_rps:.0f} rps")

已列出的记录中,每节点每秒二百次请求满足要求,二百五十次的记录则未达到延迟和错误标准。需求经过增长假设后变为每秒二千次。只按测试速率计算并均匀分配的方案共有十二个节点,失去一个区后只剩八个,即使每个节点都运行在已接受的测试速率,也只能承接每秒一千六百次请求,低于预测需求。

考虑余量与故障的方案则每区放置八个节点,总计二十四个。失效后剩余十六个节点,按较低规划速率合计可提供每秒二千二百四十次,距离这条运行限制还剩二百四十次。这个结果依赖请求组合固定、节点容量能够独立叠加以及依赖服务仍可用等假设;在形成采购决定或对外可靠性承诺之前,需要用实测和故障演练逐项替换这些前提。

延伸阅读

来源