#容量评估与规划
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。
面试官:大促前多久开始准备?
小张:至少提前一个月。
一周评估方案,两周扩容,一周压测验证。
【面试官手记】
小张这场面试的亮点:
知道容量评估三步法
知道峰值因子的概念
知道提前规划的重要性
容量评估是P6工程师必备知识点,能完整回答的候选人,说明有大促保障经验。
容量评估的核心是提前规划 + 动态扩容。记住三个要点:
- QPS估算:DAU × 人均访问 × 峰值因子
- 机器计算:峰值QPS / 单机QPS × 冗余因子
- 全链路评估:服务 + 数据库 + 缓存 + 中间件
容量是大促稳定的基石,宁可多准备,不可临时抱佛脚。