2375 字
约 7 分钟
17
S15-A24 · 容量水位:什么时候该扩、扩多少

一个服务的水位一直在 70%,团队觉得"还有余量",于是没扩。某天上游做了一次促销,流量涨了 40%,服务在十分钟内开始超时——因为 70% 是"平均水位",而峰值早就贴着上限。

水位是一个典型的被平均值掩盖的指标。扩容决策依赖的不是"平均用了多少",而是"峰值离上限还有多远",以及"从当前状态涨到上限需要多久"。前者决定风险,后者决定反应时间。


1. 四个水位口径

口径 含义 用途
平均水位 日均或小时均值 成本与利用率
峰值水位 一定窗口内的最高值 风险判断
分位水位 P95/P99 的用量 排除瞬时尖刺
水位斜率 上涨速度 反应时间

第二项与第三项要一起看:P99 高但峰值不高,说明有偶发尖刺(可能是批任务);峰值远高于 P99,说明有明显的流量尖峰,需要按峰值配。

四个口径还有一个共同前提:水位是"用了多少"还是"离上限多远",两者不能混称。资源打满时水位 100%,而延迟超标时资源可能只用 50%——这是两套完全不同的风险,看板上应当分开呈现。

第四项最容易被忽略:水位从 70% 涨到 95% 用了多久。如果只用五分钟,那么"还有 30% 余量"这句话没有任何意义,因为扩容需要的时间可能超过它。

2. 扩容触发的三条判据

判据 阈值示例 优先级
峰值水位 > 80% 连续 3 天 中
峰值水位 + 斜率 一个扩容周期内会到 95% 高
延迟或错误率 P99 超 SLA 最高

触发条件还要考虑"持续时间":单点尖峰不应触发扩容,否则每天都会扩缩一次。所以阈值应当带上持续时长或分位条件,把噪声过滤掉。

第三条不是"容量"指标,但它是最真实的信号:容量不足的第一表现往往是延迟,而不是资源打满。等到资源打满再扩,已经晚了一步。

// 扩容规则:三类判据并联,任一条满足即触发
scale_out:
  - metric: cpu_peak_7d
    condition: "> 0.80"
    action: "+1 实例"
  - metric: projected_peak
    condition: "> 0.95 within 7d"     # 按斜率外推
    action: "+2 实例,提前"
  - metric: p99_latency
    condition: "> sla * 1.2 for 3d"
    action: "+1 实例,并排查下游"

3. 扩多少:三个算法

算法 做法 适用
按比例 目标水位 60%,实例数 = 当前 × 峰值 / 0.6 无状态、线性扩展
按余量 保证"一个实例故障仍能承载" 高可用要求
按增长 按未来一个季度的预期峰值配 增长明确的业务

这个算法还有一层含义:"能承载 N-1"决定了单实例的承载能力要求,而这是压测出来的固定值,不能靠加实例改变。所以余量不足时,正确的动作可能是"提高单实例效率"而不是"加机器"。

第二项的价值在于故障场景:三个实例里挂一个,剩下两个能否承载?如果不能,说明余量实际为零。所以"能承载 N-1"是一个比"水位 60%"更硬的要求。

// 两种算法的差异(当前峰值 1000 QPS,单实例承载 400)
按比例(目标 60%):需要 1000/0.6/400 ≈ 5 个实例
按 N-1 余量     :需要满足 2 个承载 1000 → 单实例 500,需重新压测
// 结论:N-1 要求实际上抬高了"单实例承载能力"的门槛,不能只靠加实例解决

4. 水位与共享资源

资源 水位是否可随实例线性扩展
无状态计算 是
数据库连接 否(连接总数固定)
缓存带宽 部分(带宽有限)
下游调用配额 否(配额按账号算)

共享资源的清单值得单独维护一张,扩容前逐项过一遍:

// 扩容前必须核对的共享资源清单
- 数据库:连接数上限、CPU、慢查询
- 缓存:连接数、带宽、内存水位
- 消息队列:分区数(决定消费并行度上限)
- 下游 API:配额、对方限流阈值
- 网络:出口带宽、NAT 端口
// 任一项水位 > 70% → 扩容前先解决它,否则扩了无益

第二项是扩容最常见的失效点:应用扩了一倍,数据库连接数没变,于是每个实例分到的连接减半,吞吐反而下降。

// 扩容前先确认共享资源的水位
mysql -e "SHOW GLOBAL STATUS LIKE 'Threads_connected'"
mysql -e "SHOW VARIABLES LIKE 'max_connections'"
redis-cli info clients | grep connected_clients
// 若连接水位已 > 70%,扩应用实例前必须先解决连接池或加只读副本

5. 缩容与扩容的不对称

方向 风险
扩容 需要预热、冷启动、缓存重建
缩容 需要优雅摘流、连接迁移
频繁扩缩 缓存反复冷启,命中率长期偏低

还有一类隐性成本:扩缩容会触发配置下发与健康检查风暴,实例越多,每次变更的传播成本越大。所以"实例数"本身也有一个合适的上限,不是越细越好。

第三项常被忽略:自动扩缩容如果过于灵敏,会让缓存一直处于半冷状态。这类系统的延迟看起来"随机抖动",实际是扩缩容在反复清空本地缓存。

// 扩缩容的三个稳定参数
scale_up_cooldown: 3m      # 扩完至少观察 3 分钟再考虑再扩
scale_down_cooldown: 15m   # 缩容更保守,避免抖动
min_instances: 3           # 下限,避免缩到无法承载故障

水位还应当与"业务指标"对齐看:同样的 CPU 水位,在请求分布不同时代表的余量完全不同。低代价请求与高代价请求混在一起时,CPU 水位 60% 可能已经离峰值很近——因为峰值时段的大请求正在挤压计算能力。

6. 排错:五种"水位不高却过载"

现象 成因 处理方向
平均 70% 但经常超时 峰值远高于平均 按峰值与斜率判断
扩了实例吞吐没涨 共享资源瓶颈 查连接与下游配额
夜间频繁缩容白天再扩 冷却时间太短 拉长缩容冷却
单实例负载不均 长连接未重新分配 摘流重连或分批重启
延迟高但 CPU 低 等待下游或锁 看链路耗时而非资源

第四项在长连接服务里很常见:扩容后新实例分不到流量,因为老连接一直粘在旧实例上。表现为"实例数增加了但单实例负载没降"。

7. 边界:什么情况不必精细管水位

场景 为什么 做法
流量平稳且远低于容量 无风险 只做月度检查
批处理(可延后) 慢一点无影响 只看总时长
资源冗余充足 无压力 保留告警即可
短生命周期服务 来不及扩 按峰值一次配足

最后一条与决策节奏有关:扩容应当有预案的分档,而不是临场算。比如"峰值 80% 加 1、90% 加 2 并提前通知"——写成档位后,夜间值班的人不需要重新做一遍计算。

判断口径一句话:从当前水位涨到上限,需要多久。时间短于扩容周期,就必须提前扩;远长于扩容周期,才能等告警。

还有一条与成本有关的边界:水位不应追求"刚好"。把水位常年压到 85% 看起来省钱,但一次流量波动就会击穿,且没有任何时间做扩容——"留多少余量"本质是一次风险定价,而不是纯粹的成本优化。


8. 自测

  1. 四个水位口径中,哪两项必须一起看?为什么"P99 高但峰值不高"指向批任务?
  2. 为什么"水位斜率"比"当前水位"更能决定扩容时机?
  3. "能承载 N-1"为什么比"目标水位 60%"更硬?它对单实例能力提出了什么额外要求?
  4. 为什么应用扩容后吞吐可能反而下降?扩容前该先查哪两个数?
  5. 缩容为什么比扩容更需要冷却时间?过于灵敏的自动扩缩会造成什么现象?
  6. 判断"是否需要精细管水位"的那个问题是什么?
S15-A24 · 容量水位:什么时候该扩、扩多少
https://www.clxhxhhr.top/posts/6439/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。