全链路压测设计

2019年某电商平台在双十一前做全链路压测,压测团队为了模拟真实用户行为,在压测脚本里用了一个真实的用户ID。

结果悲剧了:压测脚本向这个用户发送了1万次订单创建请求,用户余额被扣成了负数,优惠券被消耗殆尽。

更严重的是:压测数据直接写入了生产数据库,影响了真实用户的数据。

这次事故导致平台赔偿用户约5万元,压测负责人被开除。

【面试官手记】

全链路压测是保障大促稳定的关键手段。我面试过的候选人里,能说清楚"压测数据隔离"的不超过30%,能说出"压测方案"的不超过20%。全链路压测的关键是压测数据不能污染生产

一、全链路压测的四大挑战 🔴

1.1 四大挑战

全链路压测四大挑战:

1. 数据隔离
   - 压测数据不能影响真实用户
   - 压测数据不能被业务逻辑误用
   - 典型场景:余额、优惠券、库存

2. 流量构造
   - 如何模拟真实的用户行为
   - 如何构造峰值流量
   - 如何保证压测请求的分布合理

3. 链路追踪
   - 如何识别压测请求
   - 如何追踪压测请求的完整链路
   - 如何隔离压测数据的存储

4. 监控告警
   - 如何设置压测监控指标
   - 如何识别压测导致的异常
   - 如何在大促前发现系统瓶颈

1.2 压测目标

压测目标设定:

性能指标:
- 接口P99 < 500ms
- 系统吞吐量 > 峰值QPS的1.5倍
- 错误率 < 0.1%

容量指标:
- 支持峰值QPS × 1.5的流量
- 数据库连接数 < 最大连接的80%
- 中间件处理能力 > 峰值1.5倍

稳定性指标:
- 压测后系统无异常
- 无数据污染
- 无配置变更

二、压测数据隔离 🔴

2.1 标识压测请求

// 压测标识Header
public class PressureTestInterceptor {

    private static final String PRESSURE_TEST_HEADER = "X-Pressure-Test";
    private static final String PRESSURE_TEST_MARK = "true";

    @Autowired
    private PressureTestConfig pressureTestConfig;

    public boolean isPressureTest(HttpServletRequest request) {
        String header = request.getHeader(PRESSURETest_HEADER);
        return pressureTestConfig.isEnabled() && PRESSURE_TEST_MARK.equals(header);
    }
}

2.2 压测数据构建

压测数据构建策略:

1. 用户数据
   - 构建10万+压测用户
   - 用户ID前缀标记:PT_123456
   - 隔离数据库Schema

2. 商品数据
   - 复制1000个商品作为压测商品
   - 商品ID前缀标记:PT_GOODS_
   - 库存充足,不影响真实库存

3. 订单数据
   - 订单ID使用压测前缀
   - 隔离到压测数据库
   - 定时清理

2.3 压测数据库

// 动态数据源切换
public class PressureTestDataSourceFilter {

    private static final String PRESSURE_TEST_DB = "pt_db";

    @Around("execution(* com.example.mapper.*.*(..))")
    public Object switchDataSource(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = getRequest();
        if (isPressureTest(request)) {
            // 切换到压测数据库
            DynamicDataSource.setDataSource(PRESSURE_TEST_DB);
        } else {
            DynamicDataSource.setDataSource("main_db");
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }
}

三、流量构造 🔴

3.1 JMeter配置

JMeter压测配置:

线程组:
- 线程数:1000
- Ramp-Up时间:300秒
- 循环次数:无限
- 持续时间:30分钟

HTTP请求默认值:
- 域名:api.example.com
- 协议:https
- 路径:/v1/order/create

Header:
- Content-Type: application/json
- X-User-Id: ${userId}
- X-Pressure-Test: true

用户变量:
- userId: PT_${__time()}${__threadNum}

3.2 流量模型

真实流量模型:

基础流量:日均QPS × 峰值因子
波动曲线:符合二八法则
地域分布:一二三线城市比例

压测流量分配:
- 搜索:30%
- 商品详情:40%
- 下单:15%
- 支付:10%
- 其他:5%

3.3 阶梯压测

压测节奏:

阶段1:10%目标流量
- 持续时间:10分钟
- 目标:验证基础功能

阶段2:50%目标流量
- 持续时间:10分钟
- 目标:验证扩容能力

阶段3:100%目标流量
- 持续时间:10分钟
- 目标:验证峰值承载

阶段4:150%目标流量
- 持续时间:5分钟
- 目标:找出系统上限

阶段5:100%目标流量
- 持续时间:30分钟
- 目标:稳定性验证

四、压测监控 🟡

4.1 压测监控指标

压测监控指标:

应用层:
- QPS:每秒请求数
- 响应时间:P50/P90/P99
- 错误率:按HTTP状态码
- JVM:CPU/内存/GC

中间件层:
- Redis:连接数/命中率/QPS
- MySQL:连接数/慢查询/QPS
- Kafka:积压量/消费延迟

基础设施层:
- CPU使用率
- 内存使用率
- 网络带宽
- 磁盘IO

4.2 压测告警

# Prometheus压测告警
groups:
- name: pressure-test-alerts
  rules:
  - alert: PressureTestErrorRateHigh
    expr: |
      sum(rate(http_requests_total{pt="true",status=~"5.."}[5m])) /
      sum(rate(http_requests_total{pt="true"}[5m])) > 0.01
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "压测错误率超过1%"

  - alert: PressureTestResponseSlow
    expr: histogram_quantile(0.99, rate(http_request_duration_ms_bucket{pt="true"}[5m])) > 500
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "压测P99响应时间超过500ms"

五、压测结果分析 🟡

5.1 瓶颈分析

压测瓶颈分析:

步骤1:查看整体QPS和响应时间
- 如果QPS上不去:带宽或负载均衡瓶颈
- 如果响应时间高:代码或数据库瓶颈

步骤2:查看各链路耗时
- 定位耗时最长的服务
- 查看该服务的日志和监控

步骤3:查看数据库指标
- 连接数是否耗尽
- 慢查询是否增多
- 锁等待是否增加

步骤4:查看中间件指标
- Redis连接数/命中率
- Kafka积压量
- MQ消费延迟

5.2 优化建议

常见瓶颈及优化建议:

1. 数据库瓶颈
   - 增加连接池大小
   - 优化慢查询
   - 增加只读实例

2. 中间件瓶颈
   - Redis集群扩容
   - Kafka分区增加
   - MQ消费者增加

3. 应用瓶颈
   - 应用服务器扩容
   - 优化代码逻辑
   - 开启异步处理

4. 网络瓶颈
   - 增加带宽
   - 使用CDN
   - 开启压缩

六、生产避坑 🟡

6.1 全链路压测的五大坑

坑1:压测数据污染生产

问题:压测数据写入了生产数据库
场景:用真实用户ID做压测
解决方案:
- 使用压测专属用户数据
- 压测数据加隔离标识
- 压测使用独立数据库

坑2:压测影响真实用户

问题:压测消耗了真实库存、优惠券
场景:压测商品和真实商品混用
解决方案:
- 压测商品和真实商品隔离
- 压测库存单独准备
- 优惠券系统接入压测标识

坑3:压测流量不真实

问题:压测流量和真实流量差异大
场景:只用简单请求,没模拟真实用户行为
解决方案:
- 构建真实用户行为模型
- 分布比例要合理
- 包含正常和异常请求

坑4:压测没有发现瓶颈

问题:压测通过了,但大促时还是崩了
场景:压测流量不够大,没有触发瓶颈
解决方案:
- 压测到150%目标流量
- 分层压测每个环节
- 大促前再次压测

坑5:压测后没有清理

问题:压测数据没有清理,占用存储
场景:压测订单保留了几个月
解决方案:
- 压测后自动清理
- 设置清理定时任务
- 清理后验证

6.2 压测检查清单

压测前检查:
- [ ] 压测数据准备完成
- [ ] 压测数据库隔离
- [ ] 监控告警配置
- [ ] 回滚方案就绪
- [ ] 值班人员到位
- [ ] 压测脚本Review

压测中检查:
- [ ] 错误率监控
- [ ] 响应时间监控
- [ ] 资源使用监控
- [ ] 压测数据隔离验证

压测后检查:
- [ ] 数据清理验证
- [ ] 系统无异常
- [ ] 性能瓶颈报告
- [ ] 优化计划

七、真实面试回放 🟡

面试官:全链路压测怎么做?

候选人(小张):四个步骤:

一是压测数据准备。构建10万+压测用户,1000个压测商品,全部加PT_前缀。

二是压测数据隔离。压测请求带X-Pressure-Test Header,根据Header切换到压测数据库。

三是流量构造。按真实流量模型构造JMeter脚本,包含搜索、下单、支付等。

四是压测监控。设置错误率、响应时间、资源使用等监控指标。

面试官:压测数据怎么隔离?

小张:三个层面:

一是请求标识。压测请求带X-Pressure-Test Header。

二是数据存储。压测数据存在单独的Schema,查询和写入都根据Header切换。

三是中间件隔离。Redis、Kafka都有压测Topic,消息带压测标识。

面试官:怎么保证压测数据不污染生产?

小张:两个措施:

一是用户隔离。压测用户ID用PT_前缀,所有查询都检查前缀。

二是余额隔离。压测余额单独准备,不使用真实余额。

【面试官手记】

小张这场面试的亮点:

  1. 知道压测四步骤

  2. 知道数据隔离的三个层面

  3. 知道用户ID前缀隔离的方法

全链路压测是P7工程师必备技能,能完整回答的候选人,说明有大促保障经验。

全链路压测的核心是压测数据隔离 + 流量真实。记住三个要点:

  1. 数据隔离:压测数据、压测数据库、压测标识
  2. 流量构造:真实用户行为模型、分布比例合理
  3. 监控分析:找出瓶颈、制定优化计划

全链路压测是大促保障的必备手段。