文章

容量规划方法论

容量规划方法论

一句话:容量规划是用数据回答”系统能扛多少量、什么时候要扩容、扩多少”——不是等系统扛不住了才扩,而是提前推演、预留余量、弹性伸缩。

概述

JD 直接提到”容量规划”,这是 SRE 的核心职责之一。容量规划解决三个问题:

  1. 当前能扛多少? → 基准测试 + 容量模型
  2. 什么时候不够? → 趋势预测 + 容量预警
  3. 怎么扩? → 弹性伸缩 + 预留策略

容量规划全景

graph TD
    BASE[基准测试] --> MODEL[容量模型]
    TREND[趋势预测] --> FORECAST[容量预测]
    MODEL --> FORECAST
    FORECAST --> ALERT[容量预警]
    ALERT --> SCALE[扩容决策]
    SCALE --> ELASTIC[弹性伸缩]
    SCALE --> MANUAL[人工扩容]
    ELASTIC --> HPA[HPA/VPA/KEDA]
    MANUAL --> CAP[容量审批]

第一步:基准测试(了解单节点能力)

压测方法论

压测分层:
  ├── 组件级压测:单服务 / 单中间件的极限能力
  │   ├── CPU bound:QPS vs CPU 利用率
  │   ├── Memory bound:QPS vs 内存增长曲线
  │   ├── IO bound:QPS vs 磁盘 IO / 网络 IO
  │   └── Connection bound:QPS vs 连接数

  ├── 链路级压测:端到端调用链的瓶颈定位
  │   ├── 逐层加压,找出链路中最弱的一环
  │   └── 关注级联效应(上游压垮下游)

  └── 全链路压测:模拟真实流量
      ├── 影子库 / 影子队列(不污染生产数据)
      ├── 流量回放(录制线上请求重放)
      └── 逐步加压 + 降压(观察恢复性)

压测工具选型

工具类型适用场景特点
wrk / wrk2HTTP 压测单接口性能轻量、支持延迟分布
vegetaHTTP 压测单接口/批量Go 编写、支持多 URL
k6HTTP/gRPC 压测复杂场景脚本化、支持多协议
Locust分布式压测全链路Python 脚本、Web UI
JMeter全协议复杂测试计划GUI、插件丰富
tc网络限制模拟弱网Linux 内核工具
stress-ng系统级CPU/内存/IO 压测不依赖应用

压测关键指标

# 压测报告必须包含
metrics:
  throughput:
    qps: "请求/秒(越高越好)"
    tps: "事务/秒(端到端)"
  
  latency:
    p50: "中位数延迟"
    p90: "90% 请求延迟"
    p99: "99% 请求延迟(SLO 关注点)"
    p999: "99.9% 请求延迟"
  
  resource_usage:
    cpu: "CPU 利用率(% 核心数)"
    memory: "内存使用(RSS + 缓存)"
    disk_io: "磁盘 IOPS + 吞吐"
    network_io: "网络带宽利用率"
    connections: "活跃连接数"
  
  error_rate:
    http_5xx: "5xx 错误率"
    timeout: "超时率"
    reset: "连接重置率"
  
  saturation_point:
    # 系统开始降效的点
    breakpoint_qps: "QPS 达到 X 后延迟急剧上升"
    max_stable_qps: "在此 QPS 下 P99 < SLO 阈值"

第二步:容量模型(推算系统总能力)

容量计算公式

单节点最大稳定 QPS = min(
    CPU 瓶颈 QPS,      # CPU > 80% 时的 QPS
    内存瓶颈 QPS,      # 内存 > 85% 时的 QPS
    连接瓶颈 QPS,      # 连接数 > 80% max 时的 QPS
    SLO 瓶颈 QPS       # P99 > SLO 阈值时的 QPS
)

集群总 QPS = 单节点 QPS × 节点数 × 效率系数

效率系数 = 实际 QPS / (单节点 QPS × 节点数)
  # 受负载均衡、锁竞争、共享资源影响
  # 通常 0.7-0.9

容量评估表(示例)

服务单节点 QPS节点数集群 QPS日均 QPS峰值 QPS余量是否需扩
API 网关500041400080001200017%是(>80%)
订单服务2000684005000700020%
MySQL80001(M)64004000550016%
Redis500003105000600008000031%

Python 容量计算工具

from dataclasses import dataclass

@dataclass
class ServiceCapacity:
    name: str
    single_node_qps: float       # 单节点最大稳定 QPS
    node_count: int              # 当前节点数
    efficiency: float            # 效率系数 0-1
    daily_avg_qps: float         # 日均 QPS
    daily_peak_qps: float        # 日峰值 QPS
    growth_rate: float           # 周环比增长率
    
    @property
    def cluster_qps(self) -> float:
        return self.single_node_qps * self.node_count * self.efficiency
    
    @property
    def utilization(self) -> float:
        """峰值利用率"""
        return self.daily_peak_qps / self.cluster_qps
    
    @property
    def headroom(self) -> float:
        """余量"""
        return 1 - self.utilization
    
    @property
    def days_until_full(self) -> int:
        """按当前增长率,多少天后容量耗尽"""
        if self.growth_rate <= 0:
            return -1  # 不增长
        # cluster_qps = daily_peak_qps * (1 + growth_rate) ^ n
        # 求解 n
        import math
        ratio = self.cluster_qps / self.daily_peak_qps
        if ratio <= 1:
            return 0  # 已超限
        return int(math.log(ratio) / math.log(1 + self.growth_rate))
    
    def recommendation(self) -> str:
        if self.utilization > 0.8:
            return f"URGENT: 利用率 {self.utilization:.0%},需立即扩容"
        elif self.utilization > 0.7:
            return f"WARN: 利用率 {self.utilization:.0%},建议扩容"
        elif self.days_until_full < 30:
            return f"PLAN: {self.days_until_full} 天后容量耗尽,启动扩容评估"
        else:
            return f"OK: 余量 {self.headroom:.0%}{self.days_until_full} 天后需扩容"

# 使用示例
svc = ServiceCapacity(
    name="API网关",
    single_node_qps=5000,
    node_count=4,
    efficiency=0.7,
    daily_avg_qps=8000,
    daily_peak_qps=12000,
    growth_rate=0.02,  # 周环比 2%
)
print(f"集群 QPS: {svc.cluster_qps}")
print(f"利用率: {svc.utilization:.1%}")
print(f"余量: {svc.headroom:.1%}")
print(f"推荐: {svc.recommendation()}")

第三步:趋势预测(预判扩容时机)

趋势预测方法

# Prometheus 查询:预测 7 天后的 QPS
predict_linear(
  rate(http_requests_total[7d])[7d:1d],
  7 * 24 * 3600  # 预测 7 天后
)

# 预测磁盘空间耗尽时间
predict_linear(
  node_filesystem_avail_bytes{mountpoint="/"}[7d],
  7 * 24 * 3600
)

容量预警规则

# 峰值利用率 > 70% 预警
- alert: CapacityWarning
  expr: |
    (
      sum(rate(http_requests_total[5m])) by (service)
      /
      (service_capacity_max_qps * node_count * 0.7)
    ) > 1
  for: 10m
  labels:
    severity: ticket
  annotations:
    summary: "{{ $labels.service }} 容量利用率 > 70%"

# 预测 7 天后超限
- alert: CapacityForecast
  expr: |
    predict_linear(
      rate(http_requests_total[7d])[7d:1d],
      7 * 24 * 3600
    ) > service_capacity_max_qps * 0.8
  for: 1h
  labels:
    severity: ticket
  annotations:
    summary: "{{ $labels.service }} 预测 7 天后容量超限"

第四步:弹性伸缩

K8s 弹性伸缩工具

工具范围适用场景特点
HPAPod 级CPU/内存/自定义指标驱动K8s 原生,最常用
VPAPod 级资源 request 自动调整需要重启 Pod
KEDAPod 级事件驱动(Kafka/Redis/PG 队列深度)支持缩到 0
Cluster Autoscaler节点级Pod Pending 触发节点扩容云厂商集成
Karpenter节点级更快的节点供给灵活的节点选择策略

HPA 配置示例

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-gateway-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-gateway
  minReplicas: 4        # 最少 4 副本(保证 HA)
  maxReplicas: 20       # 最多 20 副本(成本上限)
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 65    # CPU > 65% 扩容
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75    # 内存 > 75% 扩容
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "2000"      # 每副本 > 2000 QPS 扩容
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30   # 30 秒内不重复扩容
      policies:
        - type: Percent
          value: 100        # 每次最多扩到当前 2 倍
          periodSeconds: 60
        - type: Pods
          value: 4          # 每次最多加 4 个 Pod
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300  # 5 分钟稳定期才缩容
      policies:
        - type: Percent
          value: 25         # 每次最多缩 25%
          periodSeconds: 120

容量预留策略

三级预留:
  L1: 弹性伸缩自动覆盖(日常波动 ±30%)
  L2: 预扩容覆盖(大促/活动前人工扩容 50-100%)
  L3: 紧急扩容预案(突发流量 > 200% 时的应急方案)

预留余量标准:
  核心服务:峰值 + 50% 余量
  非核心服务:峰值 + 30% 余量
  有状态服务(DB/Redis):峰值 + 30% + 硬件级冗余

常见问题 / 坑点

问题原因解决方案
压测结果和生产不一致压测环境 ≠ 生产环境用流量回放 + 影子库在准生产环境压测
只压单接口不看链路单接口 OK 但链路有瓶颈做全链路压测,关注最弱环节
弹性伸缩太慢HPA 周期长 + Pod 启动慢预扩容 + 优化镜像/启动时间 + KEDA
缩容导致服务抖动缩容太激进增加缩容稳定期(5-10 min)+ 慢速缩容
容量模型不准效率系数拍脑袋用生产数据校准效率系数
只看 QPS 不看延迟QPS 没到但 P99 已超 SLO以 SLO 为天花板(P99 < SLO 时的 QPS 才是真容量)

关联知识

参考资源

学习时间

内容日期状态
容量规划方法论2026-08-03完成:压测、模型、预测、弹性伸缩
实际压测待实践

状态

  • 压测方法论
  • 容量模型与计算
  • 趋势预测
  • 弹性伸缩配置
  • 容量预留策略
  • 落地容量度量看板