容量评估与规划

2022年双十一前两周,某电商平台预估今年的订单量会增长50%,于是紧急扩容了服务器。

但大促当天,系统的QPS还是超出了预期——预估值基于历史数据,但今年的新用户增长超出了所有人的预期。

更可怕的是:扩容来不及了,服务器已经在满负荷运行,但还是扛不住流量。

这次故障导致大促当天系统崩溃4小时,直接损失约1亿元。

【面试官手记】

容量评估是保障大促稳定的关键。我面试过的候选人里,能说清楚"容量计算"的有30%,能说清楚"扩容方案"的有25%,能说清楚"性能指标"的有20%。容量评估的关键词是提前规划 + 动态扩容

一、容量评估指标 🔴

1.1 核心性能指标

容量评估核心指标:

1. QPS(Queries Per Second)
   - 每秒查询数
   - 衡量系统吞吐量
   - 示例:商品接口 QPS = 10000

2. TPS(Transactions Per Second)
   - 每秒事务数
   - 衡量系统处理能力
   - 示例:下单 TPS = 1000

3. RT(Response Time)
   - 平均响应时间
   - 衡量系统性能
   - 示例:接口 RT = 50ms

4. 并发用户数
   - 同时在线用户数
   - 估算公式:并发 = QPS × 平均响应时间

关系公式:
- QPS = 1000, RT = 100ms
- 并发数 = 1000 × 0.1 = 100
- 每台服务器处理能力 = 1000 QPS
- 需要服务器数 = 预估QPS / 单机QPS

1.2 性能计算公式

容量计算公式:

1. QPS估算
   预估QPS = DAU × 每天人均访问次数 / 每天秒数
   示例:
   - DAU = 100万
   - 每天人均访问10次
   - 每天秒数 = 86400
   - 预估QPS = 1000000 × 10 / 86400 ≈ 116 QPS

2. 峰值QPS估算
   峰值QPS = 平均QPS × 峰值因子
   示例:
   - 平均QPS = 116
   - 峰值因子 = 10(大促峰值)
   - 峰值QPS = 116 × 10 = 1160 QPS

3. 机器数量估算
   机器数 = 峰值QPS / 单机QPS × 冗余因子
   示例:
   - 峰值QPS = 1160
   - 单机QPS = 100
   - 冗余因子 = 1.5
   - 机器数 = 1160 / 100 × 1.5 = 18 台

4. 数据库连接数估算
   连接数 = 线程池大小 × 机器数
   示例:
   - 线程池大小 = 50
   - 机器数 = 18
   - 连接数 = 50 × 18 = 900

1.3 大促容量估算示例

// 大促容量评估
@Service
public class CapacityAssessmentService {

    /**
     * 大促容量评估
     */
    public CapacityReport assessForPromotion(PromotionPlan plan) {
        CapacityReport report = new CapacityReport();

        // 1. 预估DAU
        long estimatedDAU = plan.getBaseDAU() * plan.getGrowthRate();
        report.setEstimatedDAU(estimatedDAU);

        // 2. 预估峰值QPS
        long peakQPS = estimatePeakQPS(estimatedDAU, plan.getPeakHours());
        report.setPeakQPS(peakQPS);

        // 3. 评估各服务
        for (ServiceConfig service : plan.getServices()) {
            ServiceCapacity serviceCapacity = assessService(service, peakQPS);
            report.addServiceCapacity(service.getName(), serviceCapacity);
        }

        // 4. 评估数据库
        DBCapacity dbCapacity = assessDatabase(peakQPS);
        report.setDbCapacity(dbCapacity);

        // 5. 评估缓存
        CacheCapacity cacheCapacity = assessCache(estimatedDAU);
        report.setCacheCapacity(cacheCapacity);

        // 6. 评估网络带宽
        BandwidthCapacity bandwidthCapacity = assessBandwidth(peakQPS);
        report.setBandwidthCapacity(bandwidthCapacity);

        // 7. 输出扩容建议
        List<String> suggestions = generateSuggestions(report);
        report.setSuggestions(suggestions);

        return report;
    }

    /**
     * 预估峰值QPS
     */
    private long estimatePeakQPS(long dau, int peakHours) {
        // 峰值时段:2小时
        int peakDurationSeconds = peakHours * 3600;

        // 峰值QPS = DAU × 人均访问次数 × 峰值集中度 / 峰值时长
        long avgDailyQPS = dau * 10 / 86400;

        // 峰值因子:大促通常是平均的10-20倍
        int peakFactor = 15;

        return avgDailyQPS * peakFactor;
    }

    /**
     * 评估服务容量
     */
    private ServiceCapacity assessService(ServiceConfig service, long peakQPS) {
        ServiceCapacity capacity = new ServiceCapacity();

        // 单机QPS
        int singleMachineQPS = service.getSingleMachineQPS();

        // 需要的机器数
        int requiredMachines = (int) Math.ceil(
            (double) peakQPS / singleMachineQPS * 1.5  // 冗余1.5倍
        );

        // 当前机器数
        int currentMachines = service.getCurrentMachines();

        // 扩容建议
        if (requiredMachines > currentMachines) {
            capacity.setNeedExpand(true);
            capacity.setExpandCount(requiredMachines - currentMachines);
        }

        return capacity;
    }
}

二、资源评估方法 🔴

2.1 服务资源评估

服务资源评估:

1. CPU评估
   - 单核处理能力:1000 QPS(CPU密集型)
   - 评估公式:需要的CPU核数 = 峰值QPS / 单核QPS
   - 示例:峰值QPS = 10000,单核QPS = 1000
   - 需要CPU核数 = 10000 / 1000 = 10核

2. 内存评估
   - 每个请求占用:50KB(Java应用)
   - 并发请求数:1000
   - 需要内存 = 50KB × 1000 = 50MB(基础)
   - 加上JVM堆:2GB
   - 总计:约3GB

3. 磁盘评估
   - 日志大小:100MB/小时
   - 大促持续时间:12小时
   - 需要磁盘 = 100MB × 12 × 2(备份) = 2.4GB

2.2 数据库资源评估

数据库资源评估:

1. 连接数评估
   - 单机最大连接:2000
   - 应用线程数:50
   - 应用机器数:20
   - 需要连接数 = 50 × 20 = 1000
   - 需要的数据库实例数 = 1000 / 2000 = 1个

2. QPS评估
   - 单机MySQL QPS:10000
   - 峰值QPS:50000
   - 需要MySQL机器数 = 50000 / 10000 × 1.5 = 8台

3. 存储评估
   - 单条数据大小:1KB
   - 每日新增数据:100万条
   - 数据保留时间:90天
   - 需要存储 = 1KB × 100万 × 90 = 90GB

2.3 缓存资源评估

缓存资源评估:

1. Redis内存评估
   - 缓存数据量:1GB
   - 连接数:10000
   - Redis单机内存:16GB
   - 需要Redis机器数:1台(预留2倍)
   - 推荐:2台(主从)

2. 热点数据评估
   - 热点数据占比:20%
   - 热点数据量:200MB
   - 本地缓存大小:100MB

3. 带宽评估
   - QPS:10000
   - 单次请求大小:1KB
   - 需要的带宽 = 10000 × 1KB = 10MB/s = 80Mbps

三、扩容方案 🟡

3.1 扩容策略

扩容策略:

1. 垂直扩容(Scale Up)
   - 增加单机资源:CPU、内存、磁盘
   - 优点:简单,不需要改代码
   - 缺点:有上限
   - 适用:初期、中期

2. 水平扩容(Scale Out)
   - 增加机器数量
   - 优点:无上限
   - 缺点:需要无状态化
   - 适用:中后期

3. 混合扩容
   - 先垂直扩容撑一撑
   - 再水平扩容扩展

3.2 自动扩容配置

# Kubernetes HPA配置
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-service
  minReplicas: 10
  maxReplicas: 100
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 300  # 扩容冷却时间5分钟
      policies:
      - type: Percent
        value: 100
        periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 600  # 缩容冷却时间10分钟
// 应用层自动扩容
@Service
public class AutoScaleService {

    @Autowired
    private MonitorService monitorService;

    @Autowired
    private DeployService deployService;

    private static final double CPU_THRESHOLD = 70.0;
    private static final double MEMORY_THRESHOLD = 80.0;
    private static final int MIN_REPLICAS = 10;
    private static final int MAX_REPLICAS = 100;

    @Scheduled(fixedRate = 30000)  // 每30秒检查一次
    public void checkAndScale() {
        Metrics metrics = monitorService.getCurrentMetrics();

        double cpuUsage = metrics.getCpuUsage();
        double memoryUsage = metrics.getMemoryUsage();
        int currentReplicas = metrics.getReplicas();

        // 计算目标副本数
        int targetReplicas = calculateTargetReplicas(
            metrics, cpuUsage, memoryUsage, currentReplicas);

        // 限制范围
        targetReplicas = Math.max(MIN_REPLICAS,
            Math.min(MAX_REPLICAS, targetReplicas));

        if (targetReplicas != currentReplicas) {
            if (targetReplicas > currentReplicas) {
                log.info("扩容:{} -> {}", currentReplicas, targetReplicas);
                deployService.scaleUp(targetReplicas);
            } else {
                log.info("缩容:{} -> {}", currentReplicas, targetReplicas);
                deployService.scaleDown(targetReplicas);
            }
        }
    }

    private int calculateTargetReplicas(Metrics metrics,
                                        double cpuUsage,
                                        double memoryUsage,
                                        int currentReplicas) {
        // 基于CPU计算
        double cpuRatio = cpuUsage / CPU_THRESHOLD;

        // 基于内存计算
        double memoryRatio = memoryUsage / MEMORY_THRESHOLD;

        // 取较大值,额外加10%缓冲
        double ratio = Math.max(cpuRatio, memoryRatio);
        int target = (int) Math.ceil(currentReplicas * ratio * 1.1);

        return target;
    }
}

四、性能压测 🟡

4.1 压测方案

# JMeter压测配置
test_plan:
  name: 大促压测

thread_group:
  name: 用户下单场景
  threads: 1000           # 1000并发
  ramp_up: 300             # 5分钟预热
  duration: 3600           # 持续1小时

http_request:
  name: 创建订单
  method: POST
  url: /api/order/create
  body:
    productId: "${productId}"
    quantity: 1
    userId: "${userId}"

response_assertion:
  - check: status_code
    expected: 200
  - check: response_time
    less_than: 500  # 500ms内响应

listeners:
  - type: summary_report
    output: summary.html
  - type: response_times_percentiles
    percentiles: 50,90,95,99

4.2 压测结果分析

压测结果分析:

1. 达标标准
   - P99响应时间 < 500ms
   - 错误率 < 0.1%
   - CPU使用率 < 80%
   - 内存使用率 < 85%

2. 瓶颈识别
   - CPU 100%:CPU瓶颈,需要扩容
   - 内存持续增长:内存泄漏
   - 连接池耗尽:数据库瓶颈
   - 线程堆积:下游服务慢

3. 扩容决策
   - 压测QPS = 10000,目标是20000
   - 需要扩容比例 = 20000 / 10000 = 2倍
   - 建议扩容后压测验证

五、生产避坑 🟡

5.1 容量评估的五大坑

坑1:只按历史数据估算

问题:按去年数据估算今年
场景:今年增长超出预期
解决方案:
- 按最坏情况估算
- 预留扩容空间
- 设置监控告警

坑2:忽视峰值流量

问题:按平均QPS准备资源
场景:峰值是平均的10-20倍
解决方案:
- 必须考虑峰值因子
- 大促峰值因子10-20
- 普通日峰值因子3-5

坑3:忽视依赖服务

问题:只评估自己的服务
场景:数据库、中间件成为瓶颈
解决方案:
- 全链路评估
- 数据库、Redis、MQ都要评估

坑4:扩容来不及

问题:大促前一周才开始扩容
场景:服务器交付需要时间
解决方案:
- 提前1个月评估
- 提前2周扩容
- 准备弹性扩容方案

坑5:忽视链路短板

问题:服务扩容了,但数据库没扩容
场景:应用QPS翻倍,数据库瓶颈
解决方案:
- 找出链路中最短的木板
- 优先扩容短板

5.2 容量评估检查清单

评估规范:
- [ ] 预估DAU和增长
- [ ] 计算峰值QPS
- [ ] 评估单机处理能力
- [ ] 计算需要的机器数

资源规范:
- [ ] 服务资源(CPU、内存)
- [ ] 数据库资源(连接数、QPS)
- [ ] 缓存资源(内存、连接数)
- [ ] 网络带宽

扩容规范:
- [ ] 扩容方案制定
- [ ] 扩容时间表
- [ ] 扩容验证
- [ ] 回滚预案

监控规范:
- [ ] 实时QPS监控
- [ ] 资源使用率监控
- [ ] 告警阈值设置

六、真实面试回放 🟡

面试官:怎么评估大促需要多少台服务器?

候选人(小张):三步:

一是估算DAU和峰值QPS。DAU乘以人均访问次数,再乘以峰值因子。

二是评估单机处理能力。单机能抗多少QPS,需要压测确定。

三是计算机器数。峰值QPS除以单机QPS,再乘以冗余因子。

面试官:峰值因子怎么确定?

小张:看业务特点。

普通日峰值因子3-5倍,大促峰值因子10-20倍。

比如平时100QPS,大促就是1000-2000QPS。

面试官:大促前多久开始准备?

小张:至少提前一个月。

一周评估方案,两周扩容,一周压测验证。

【面试官手记】

小张这场面试的亮点:

  1. 知道容量评估三步法

  2. 知道峰值因子的概念

  3. 知道提前规划的重要性

容量评估是P6工程师必备知识点,能完整回答的候选人,说明有大促保障经验。

容量评估的核心是提前规划 + 动态扩容。记住三个要点:

  1. QPS估算:DAU × 人均访问 × 峰值因子
  2. 机器计算:峰值QPS / 单机QPS × 冗余因子
  3. 全链路评估:服务 + 数据库 + 缓存 + 中间件

容量是大促稳定的基石,宁可多准备,不可临时抱佛脚。