全链路压测
概述
全链路压测是在生产环境或仿真环境中,模拟真实用户流量对整个系统进行压力测试,以评估系统的容量上限、发现瓶颈点、验证弹性能力。
核心目标
1. 容量评估 → 系统能支撑多少 QPS?
2. 瓶颈定位 → 哪个环节最先扛不住?
3. 弹性验证 → 扩容后性能线性提升?
4. 稳定性 → 长时间高压运行是否异常?
5. 大促保障 → 双11/618 预案是否有效?一、压测方案设计
1.1 压测模型
text
流量模型:
电商大促场景
├── 浏览商品:60% 流量(读密集型)
├── 加购:15% 流量
├── 下单:10% 流量(写密集型)
├── 支付:5% 流量(三方依赖)
└── 物流查询:10% 流量
压测策略
├── 爬坡压测:100 → 1000 → 5000 → 10000 QPS 逐步增加
├── 恒压压测:在目标 QPS 持续运行 30 分钟
├── 突发压测:瞬时流量 2 倍于均值
└── 极限压测:直到系统崩溃(找到上限)
目标指标
├── P99 延迟 < 500ms
├── 错误率 < 0.1%
├── CPU 使用率 < 80%
└── GC 暂停 < 200ms1.2 压测环境
| 方案 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 生产镜像 | 与生产等配 | 结果准确 | 成本高 |
| 生产读写分离 | 压测流量打到读库 | 真实度中 | 不可压写 |
| 全链路隔离 | 压测标记 + 影子库 | 可压全链路 | 改造量大 |
| 预发环境 | 预发集群 | 安全 | 配置差异 |
二、压测数据隔离
2.1 压测标记透传
java
// 压测标记:在请求入口注入
@Component
public class StressTestFlagFilter implements Filter {
private static final String STRESS_HEADER = "X-Stress-Test";
private static final ThreadLocal<Boolean> STRESS_FLAG = new ThreadLocal<>();
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String flag = req.getHeader(STRESS_HEADER);
if ("true".equals(flag)) {
STRESS_FLAG.set(true);
}
try {
chain.doFilter(request, response);
} finally {
STRESS_FLAG.remove();
}
}
public static boolean isStressTest() {
return Boolean.TRUE.equals(STRESS_FLAG.get());
}
}2.2 影子库/表
java
// 压测数据写入影子表
@Aspect
@Component
public class ShadowTableAspect {
@Around("@annotation(shadowTable)")
public Object routeToShadow(ProceedingJoinPoint pjp,
ShadowTable shadowTable) throws Throwable {
if (StressTestFlagFilter.isStressTest()) {
// 切换到影子表(表名 + _shadow)
String originalTable = shadowTable.value();
DynamicTableNameHolder.set(originalTable + "_shadow");
}
try {
return pjp.proceed();
} finally {
DynamicTableNameHolder.clear();
}
}
}
// 使用
@Service
public class OrderService {
@ShadowTable("t_order")
public void createOrder(OrderRequest request) {
// 压测时自动写入 t_order_shadow
orderMapper.insert(request.toOrder());
}
}2.3 三方接口 Mock
java
// 压测时 Mock 三方依赖
@Component
public class StressTestMockInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
if (StressTestFlagFilter.isStressTest()) {
// 返回 Mock 响应
String json = "{\"code\":200,\"message\":\"mock\"}";
return new SimpleClientHttpResponse(200, json);
}
return execution.execute(request, body);
}
}三、压测工具
3.1 JMeter 压测
xml
<!-- JMeter Maven 插件 -->
<plugin>
<groupId>com.lazerycode.jmeter</groupId>
<artifactId>jmeter-maven-plugin</artifactId>
<version>3.9.0</version>
<configuration>
<testFilesDirectory>${project.basedir}/src/test/jmeter</testFilesDirectory>
<resultsFileFormat>xml</resultsFileFormat>
</configuration>
</plugin>java
// JMeter 自定义 Java 采样器
public class OrderApiSampler extends AbstractJavaSamplerClient {
private RestTemplate restTemplate = new RestTemplate();
@Override
public SampleResult runTest(JavaSamplerContext context) {
SampleResult result = new SampleResult();
result.sampleStart();
try {
// 构造请求
HttpHeaders headers = new HttpHeaders();
headers.set("X-Stress-Test", "true"); // 压测标记
HttpEntity<String> entity = new HttpEntity<>(headers);
// 调用接口
ResponseEntity<String> response = restTemplate.exchange(
"http://gateway/api/orders",
HttpMethod.POST,
entity,
String.class
);
result.setSuccessful(response.getStatusCode().is2xxSuccessful());
result.setResponseData(response.getBody(), "UTF-8");
} catch (Exception e) {
result.setSuccessful(false);
result.setResponseData(e.getMessage(), "UTF-8");
} finally {
result.sampleEnd();
}
return result;
}
}3.2 Gatling 压测
scala
// Gatling 压测脚本(Scala)
class OrderSimulation extends Simulation {
val httpProtocol = http
.baseUrl("http://gateway")
.header("X-Stress-Test", "true")
.header("Content-Type", "application/json")
// 创建订单场景
val createOrderScenario = scenario("Create Order")
.exec(
http("create_order")
.post("/api/orders")
.body(StringBody("""{"productId":1,"quantity":1}"""))
.check(status.is(200))
)
// 浏览商品场景
val browseScenario = scenario("Browse Product")
.exec(
http("browse_product")
.get("/api/products/1")
.check(status.is(200))
)
setUp(
// 60% 浏览 + 40% 下单
browseScenario.inject(
rampUsersPerSec(100).to(1000).during(300) // 5 分钟爬坡
),
createOrderScenario.inject(
rampUsersPerSec(50).to(500).during(300)
)
).protocols(httpProtocol)
}四、容量评估
4.1 单机容量评估
text
目标:百万 QPS 大促保障
单机评估:
├── 4C8G 实例:约 2000 QPS(下单接口)
├── 8C16G 实例:约 5000 QPS
└── 16C32G 实例:约 10000 QPS
需要实例数:
├── 2000 QPS/台 → 需要 500 台
├── 5000 QPS/台 → 需要 200 台
└── 10000 QPS/台 → 需要 100 台
资源瓶颈:
├── CPU:序列化/反序列化(JSON)
├── 内存:GC 暂停
├── 网络:带宽(100万 QPS ≈ 500MB/s )
└── DB:连接池(200 台 × 10 = 2000 连接)4.2 容量规划表
| 服务 | 单机 QPS | 所需实例 | CPU | 内存 | DB 连接 |
|---|---|---|---|---|---|
| Gateway | 20000 | 5 | 4C | 8G | 0 |
| 用户服务 | 10000 | 10 | 4C | 8G | 100 |
| 商品服务 | 8000 | 13 | 8C | 16G | 200 |
| 订单服务 | 5000 | 20 | 8C | 16G | 300 |
| 支付服务 | 3000 | 34 | 4C | 8G | 150 |
| 库存服务 | 6000 | 17 | 4C | 8G | 200 |
| 合计 | 99 |
五、瓶颈分析
5.1 常见瓶颈
| 层级 | 瓶颈 | 指标 | 工具 |
|---|---|---|---|
| Gateway | 连接数 | connections, threads | Actuator |
| 应用 | GC 暂停 | jvm.gc.pause | GC 日志 |
| 应用 | CPU 高 | process.cpu.usage | VisualVM |
| 应用 | 慢方法 | Trace Span | SkyWalking |
| DB | 慢 SQL | > 200ms | MySQL slow log |
| DB | 连接池满 | hikaricp.timeout | Actuator |
| Redis | 热点 Key | hotkeys | redis-cli --hotkeys |
| MQ | 堆积 | consumerLag | RocketMQ Console |
5.2 真实案例
text
案例:双11 压测 — 订单服务 P99 5s
分析过程:
1. SkyWalking Trace → 下单接口调用链
2. 发现库存远程调用耗时 2s(Redis 热点 key)
3. 数据库连接池排队 1s(连接池过小)
4. 序列化 JSON 耗时 500ms(大对象)
优化方案:
1. 库存 Redis 热点 key 拆分为多个 hash slot
2. 连接池从 20 → 50
3. 引入 Protobuf 替代 JSON 序列化
优化效果:P99 从 5s → 300ms六、优化策略
6.1 常见优化方向
text
1. 缓存优化
├── 热点数据:Caffeine 本地缓存(1ms)
├── 高频读:Redis 缓存(5ms)
└── 兜底:数据库(50ms)
2. 异步化
├── 同步→MQ 异步:下单后短信通知
└── 批量处理:合并数据库写入
3. 连接池调优
├── DB:initial=10, max=50, timeout=30s
├── Redis:maxTotal=100, maxIdle=50
└── HTTP:maxTotal=200, maxPerRoute=50
4. 限流降级
├── 非核心服务直接降级
├── 核心服务配置限流阈值
└── 缓存兜底
5. JVM 调优
├── -Xms = -Xmx(避免扩容)
├── GC:G1 → ZGC(降低暂停)
└── 堆大小:4C8G 堆 4G6.2 优化效果对比
| 优化项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 本地缓存 | 50ms | 1ms | 50x |
| 异步化 | 500ms | 5ms | 100x |
| 连接池 | timeout 30% | <1% | - |
| GC 调优 | STW 500ms | STW 10ms | 50x |
| 限流降级 | 雪崩 | 降级优雅 | - |
七、压测报告
7.1 报告模板
markdown
# 全链路压测报告
## 基本信息
- 压测时间:2026-07-24 10:00 - 12:00
- 压测环境:生产镜像(等配)
- 压测场景:电商下单全链路
## 压测结果
| 指标 | 目标 | 实际 | 结论 |
|------|------|------|------|
| 目标 QPS | 100000 | 85000 | ❌ 未达标 |
| P99 延迟 | 500ms | 800ms | ❌ 超标 |
| 错误率 | 0.1% | 0.05% | ✅ |
## 瓶颈点
1. 订单服务数据库连接池满(连接数 50 → 需 200)
2. Redis 库存热点 Key(单分片 QPS 5 万)
3. 支付三方接口超时(未 Mock)
## 优化建议
1. 订单服务实例扩容 20 → 50 台
2. 库存 Key 拆分 8 个 hash slot
3. 压测时 Mock 三方支付
## 行动计划
- 优化负责人:张三
- 预计完成:2026-07-28
- 复测时间:2026-07-30八、总结
| 知识点 | 说明 |
|---|---|
| 压测模型 | 爬坡/恒压/突发/极限 |
| 数据隔离 | 压测标记 + 影子表 + Mock 三方 |
| 压测工具 | JMeter / Gatling |
| 容量评估 | 单机 QPS × 实例数 |
| 瓶颈分析 | Trace + 指标 + 慢日志 |
| 优化策略 | 缓存/异步/连接池/限流/JVM |
| 压测报告 | 指标对比 + 行动计划 |
参考链接: