系统容量规划与压测实战:从1万到100万QPS的科学扩容

系统容量规划与压测实战:从1万到100万QPS的科学扩容

大家好,我是迪哥。2026 年 618 前,我们的预估峰值从 10 万 QPS 涨到了 100 万 QPS,压测发现瓶颈在数据库连接池,扩容了 20 倍才顶住。今天聊聊,如何科学地做容量规划和压测,不让你的系统死在峰值。

为什么要做容量规划?

2023 年我们踩过坑:

  • ❌ 618 峰值预估 5 万,实际来了 20 万,系统直接雪崩
  • ❌ 为了保险,提前扩容了 10 倍,成本超了 3 倍
  • ❌ 不知道瓶颈在哪,瞎扩容,钱花了性能没上去

容量规划三步法

1. 建立性能基准线

单机性能测试:

bash 复制代码
# 用 JMeter 或者 wrk 压测
wrk -t12 -c400 -d30s http://localhost:8080/api/order

# 结果示例
Running 30s test @ http://localhost:8080/api/order
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    20.5ms   15.2ms  200.5ms   89.00%
    Req/Sec    5000.0   200.0    6000.0    90.00%
  1800000 requests in 30.00s, 2.50GB read
Requests/sec:  60000.00  ← 单机 6 万 QPS

基准表:

指标 数值
单机 QPS 60000
单机 CPU 峰值 80%
单机内存峰值 8GB
数据库最大连接数 2000

2. 预估峰值 & 冗余系数

复制代码
预估峰值 QPS = 历史峰值 × 增长系数 × 突发系数
           = 5万 × 2倍 × 1.5倍
           = 15万 QPS

需要机器数 = 预估峰值 / 单机 QPS × 冗余系数
          = 15万 / 6万 × 1.3
          ≈ 4台

但实际是 100 万,所以需要 100万 / 6万 × 1.3 ≈ 22台

经验值:

  • 日常冗余系数:1.3-1.5
  • 大促冗余系数:2-3
  • 新业务:3-5倍冗余

3. 识别瓶颈 & 扩容

压测实战:JMeter

安装 JMeter

下载:https://jmeter.apache.org/download_jmeter.cgi

测试计划

复制代码
Test Plan
└── Thread Group (1000 threads, 10s ramp-up, 300s duration)
    ├── HTTP Request (POST /api/order)
    ├── View Results Tree
    └── Summary Report

关键监控指标

层级 指标 告警阈值
系统 CPU > 80%
系统 Load Average > CPU 核数
系统 内存 > 85%
系统 网络带宽 > 80%
JVM Heap Used > 80%
JVM GC 次数 Full GC > 3次/5分钟
应用 错误率 > 0.1%
应用 P99 延迟 > 500ms
中间件 数据库连接池 > 90%
中间件 Redis 连接池 > 90%

瓶颈定位示例

现象:

  • 应用 QPS 上不去,CPU 只有 30%
  • 错误率上升,提示 GetConnectionTimeoutException

定位:

yaml 复制代码
# application.yml
spring:
  datasource:
    hikari:
      maximum-pool-size: 20  ← 太小了!

优化:

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 200  # 改成 200

全链路压测

单系统压测不够,要全链路!

复制代码
 压测工具 → 网关 → 订单服务 → 库存服务 → 数据库
                                          ↓
                                        Redis

关键点:

  1. ✅ 影子表(数据库隔离)
  2. ✅ 消息隔离(避免污染真实数据)
  3. ✅ 灰度流量(从 10% 开始逐步放大)

压测脚本:Go 版本

go 复制代码
package main

import (
    "fmt"
    "net/http"
    "sync"
    "time"
)

func main() {
    concurrency := 1000
    requests := 1000000
    var wg sync.WaitGroup
    client := &http.Client{Timeout: 10 * time.Second}

    start := time.Now()
    for i := 0; i < concurrency; i++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            for j := 0; j < requests/concurrency; j++ {
                resp, err := client.Get("http://localhost:8080/api/order")
                if err != nil {
                    continue
                }
                resp.Body.Close()
            }
        }(i)
    }
    wg.Wait()
    duration := time.Since(start)
    qps := float64(requests) / duration.Seconds()
    fmt.Printf("QPS: %.0f, Duration: %v\n", qps, duration)
}

容量规划表格

系统 单机 QPS 预估峰值 冗余系数 所需机器 现状机器 是否要扩容
订单服务 60000 100万 1.5 25 10 ✅ 是
库存服务 80000 80万 1.5 15 8 ✅ 是
用户服务 40000 30万 1.5 11 12 ❌ 否
网关 15万 100万 1.5 10 5 ✅ 是

经验总结

  1. 不要瞎拍脑袋! 基于历史数据 + 增长预期
  2. 先压测再扩容! 不然钱花了,性能没上去
  3. 容量规划是动态的! 每季度重估一次
  4. 监控是前提! 没有监控,谈何容量规划
  5. 灰度很重要! 别一上来全量,从 10% 开始

说到容量规划,我家那只叫 Docker 的哈士奇最近饭量从 1 碗涨到了 4 碗,我要是早做容量规划,就不至于三天买一次狗粮了 😂

我是迪哥,我们下期再见!


往期推荐:

相关推荐
Dicky-_-zhang7 小时前
消息队列Kafka/RocketMQ选型与高可用架构:从单体到100万TPS的演进
java·jvm
晨曦中的暮雨7 小时前
4.15腾讯 CSIG云服务产线 一面
java·开发语言
2301_781571427 小时前
Golang格式化输出占位符都有什么_Golang fmt占位符教程【通俗】
jvm·数据库·python
fake_ss1987 小时前
AI时代学习全栈项目开发的新范式
java·人工智能·学习·架构·个人开发·学习方法
茉莉玫瑰花茶7 小时前
工作流的常见模式 [ 1 ]
java·服务器·前端
未若君雅裁7 小时前
Spring AOP、日志切面与声明式事务原理
java·后端·spring
No8g攻城狮8 小时前
【人大金仓】wsl2+ubuntu22.04安装人大金仓数据库V9
java·数据库·spring boot·非关系型数据库
xiaoerbuyu12338 小时前
开源Java 邮箱 基于SpringBoot+Vue前后端分离的电子邮件
java·开发语言