本文目录导读:

下面是一个完整的Java全链路压测案例,包含架构设计、压测工具选择、代码实现和最佳实践。
案例背景
假设我们要对一个电商系统的下单流程进行全链路压测,链路包含:
用户请求 → Nginx → Spring Cloud Gateway → 用户服务 → 订单服务 → 库存服务 → 支付服务
压测架构设计
整体架构
压测架构: 压测工具: [Apache JMeter/Gatling/自研压测平台] 压测入口: Nginx集群(负载均衡) 链路组件: [Gateway, 用户服务, 订单服务, 库存服务, 支付服务] 数据支撑: [MySQL, Redis, MQ, 分布式缓存] 监控系统: [Prometheus + Grafana + 日志中心]
核心配置示例
# application.yml 压测配置
spring:
profiles: stress-test
server:
tomcat:
max-threads: 200
min-spare-threads: 50
accept-count: 100
connection-timeout: 3000
stress:
enabled: true
mode: full-link
# JMeter压测配置
Thread Group:
Number of Threads: 500
Ramp-Up Period: 60
Loop Count: 100
HTTP Request:
Protocol: http
Server: 10.0.0.100
Port: 8080
Method: POST
Path: /api/order/create
核心代码实现
压测链路服务改造
// 用户服务 - 预留压测专用接口
@RestController
@RequestMapping("/api/user")
public class UserController {
@PostMapping("/login")
public Result<UserInfo> login(@RequestBody LoginRequest request) {
// 正常业务逻辑
return userService.login(request);
}
@PostMapping("/login/test")
@StressTest (不落库,只做内存操作)
public Result<UserInfo> loginTest(@RequestBody LoginRequest request) {
// 压测专用,不操作数据库,只返回模拟数据
return Result.success(mockUserInfo(request.getUsername()));
}
}
// 订单服务 - 异步链路支持
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
@Override
@Transactional(rollbackFor = Exception.class)
public OrderResult createOrder(OrderRequest request) {
// 判断是否为压测请求
if (StressTestContext.isStressTest()) {
return createMockOrder(request);
}
try {
// 1. 创建订单
Order order = buildOrder(request);
orderMapper.insert(order);
// 2. 扣减库存
inventoryService.deductStock(order);
// 3. 调用支付
paymentService.pay(order);
return OrderResult.success(order);
} catch (Exception e) {
log.error("创建订单失败", e);
return OrderResult.fail(e.getMessage());
}
}
// 压测专用:不落库,走内存队列
private OrderResult createMockOrder(OrderRequest request) {
Order order = buildOrder(request);
order.setOrderNo("MOCK-" + System.currentTimeMillis());
// 放内存队列,不落库
mockOrderQueue.offer(order);
return OrderResult.success(order);
}
}
// 库存服务 - 缓存优先设计
@Service
@Slf4j
public class InventoryServiceImpl implements InventoryService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private InventoryMapper inventoryMapper;
@Override
public Boolean deductStock(Order order) {
// 压测请求直接返回成功
if (StressTestContext.isStressTest()) {
return true;
}
// 先查Redis
String key = "stock:" + order.getProductId();
Long stock = redisTemplate.opsForValue().decrement(key);
if (stock < 0) {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key);
throw new BusinessException("库存不足");
}
// 异步同步到数据库
asyncSyncStock(order.getProductId(), stock);
return true;
}
}
// 压测上下文工具类
@Component
public class StressTestContext {
private static final ThreadLocal<Boolean> STRESS_TEST_FLAG = new ThreadLocal<>();
private static final Queue<Order> MOCK_ORDER_QUEUE = new ConcurrentLinkedQueue<>();
private static volatile boolean isStressTestMode = false;
public static void setStressTest(boolean isStressTest) {
STRESS_TEST_FLAG.set(isStressTest);
}
public static boolean isStressTest() {
return Boolean.TRUE.equals(STRESS_TEST_FLAG.get()) || isStressTestMode;
}
public static void setStressTestMode(boolean mode) {
isStressTestMode = mode;
}
public static void clear() {
STRESS_TEST_FLAG.remove();
}
}
// 过滤器 - 识别压测请求
@Component
public class StressTestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String stressTestFlag = httpRequest.getHeader("X-Stress-Test");
if ("true".equals(stressTestFlag)) {
StressTestContext.setStressTest(true);
// 添加压测标记到MDC,便于日志追踪
MDC.put("stressTest", "true");
}
try {
chain.doFilter(request, response);
} finally {
StressTestContext.clear();
MDC.clear();
}
}
}
JMeter压测脚本配置
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.0" properties="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="电商系统全链路压测">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="SERVER" elementType="Argument">
<stringProp name="Argument.name">SERVER</stringProp>
<stringProp name="Argument.value">10.0.0.100</stringProp>
</elementProp>
<elementProp name="PORT" elementType="Argument">
<stringProp name="Argument.name">PORT</stringProp>
<stringProp name="Argument.value">8080</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<hashTree>
<!-- 线程组配置 -->
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="全链路压力测试">
<stringProp name="ThreadGroup.num_threads">500</stringProp>
<stringProp name="ThreadGroup.ramp_time">60</stringProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
<boolProp name="ThreadGroup.scheduler">true</boolProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController">
<stringProp name="LoopController.loops">-1</stringProp>
</elementProp>
<hashTree>
<!-- 登录请求 -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="用户登录">
<stringProp name="HTTPSampler.domain">${SERVER}</stringProp>
<stringProp name="HTTPSampler.port">${PORT}</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/user/login</stringProp>
<stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
<stringProp name="HTTPSampler.response_timeout">10000</stringProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="request" elementType="HTTPArgument">
<stringProp name="Argument.name">request</stringProp>
<stringProp name="Argument.value">{"username":"testUser${__Random(1,1000)}","password":"123456"}</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPSamplerProxy>
<!-- 创建订单请求 -->
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="创建订单">
<stringProp name="HTTPSampler.domain">${SERVER}</stringProp>
<stringProp name="HTTPSampler.port">${PORT}</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<stringProp name="HTTPSampler.path">/api/order/create</stringProp>
<elementProp name="HTTPsampler.Arguments" elementType="Arguments">
<collectionProp name="Arguments.arguments">
<elementProp name="token" elementType="HTTPArgument">
<stringProp name="Argument.name">token</stringProp>
<stringProp name="Argument.value">${login_token}</stringProp>
</elementProp>
<elementProp name="request" elementType="HTTPArgument">
<stringProp name="Argument.name">request</stringProp>
<stringProp name="Argument.value">{"productId":1001,"quantity":1,"userId":"testUser"}</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPSamplerProxy>
<!-- 进行链路检查 -->
<ResponseAssertion guiclass="ResponseAssertionGui" testclass="ResponseAssertion" testname="响应断言">
<collectionProp name="Asserion.test_strings">
<stringProp name="49586">success</stringProp>
</collectionProp>
<stringProp name="Assertion.test_field">Assertion.response_message</stringProp>
</ResponseAssertion>
</hashTree>
</ThreadGroup>
</hashTree>
</TestPlan>
</hashTree>
</jmeterTestPlan>
监控与性能分析
// 性能指标收集器
@Aspect
@Component
public class PerformanceMonitor {
private static final MeterRegistry meterRegistry = new SimpleMeterRegistry();
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Around("serviceLayer()")
public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
Timer.Sample sample = Timer.start(meterRegistry);
String methodName = joinPoint.getSignature().getName();
try {
Object result = joinPoint.proceed();
// 记录成功率
meterRegistry.counter("method_success_count", "method", methodName).increment();
return result;
} catch (Exception e) {
// 记录失败
meterRegistry.counter("method_failure_count", "method", methodName, "error", e.getMessage()).increment();
throw e;
} finally {
sample.stop(Timer.builder("method_execution_time")
.tag("method", methodName)
.register(meterRegistry));
}
}
}
压测数据准备
-- 创建压测用户表
CREATE TABLE stress_test_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
password VARCHAR(50),
is_stress BOOLEAN DEFAULT TRUE,
created_time TIMESTAMP
);
-- 创建压测商品表
CREATE TABLE stress_test_product (
id BIGINT PRIMARY KEY,
product_name VARCHAR(100),
price DECIMAL(10,2),
stock INT,
is_stress BOOLEAN DEFAULT TRUE
);
-- 初始化数据脚本
DELIMITER $$
CREATE PROCEDURE init_stress_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 10000 DO
INSERT INTO stress_test_user (username, password, is_stress)
VALUES (CONCAT('stress_', i), MD5('123456'), TRUE);
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
CALL init_stress_data();
压测执行步骤
环境准备
# 启动压测环境 docker-compose -f stress-test-environment.yml up -d # 初始化数据 mysql -h localhost -u root -p < init_data.sql # 启动服务(去掉本地依赖) nohup java -jar user-service.jar --spring.profiles.active=stress & nohup java -jar order-service.jar --spring.profiles.active=stress &
执行压测
# 使用JMeter命令执行
jmeter -n -t full-link-stress-test.jmx -l results.jtl -j stress-log.log -e -o report
# 使用Gatling执行
gatling.sh -s fullLink.StressTestSimulation
# 或使用自研压测平台
curl -X POST http://stress-platform:8080/api/stress/start \
-H "Content-Type: application/json" \
-d '{"scenarioId": 1, "concurrency": 500, "duration": 300}'
监控指标采集
// 压测监控数据
public class StressMetrics {
// 吞吐量
private double requestsPerSecond;
// 平均响应时间
private double averageResponseTime;
// P99响应时间
private double p99ResponseTime;
// 错误率
private double errorRate;
// CPU使用率
private double cpuUsage;
// 内存使用率
private double memoryUsage;
// 线程池状态
private int activeThreads;
private int queueSize;
}
最佳实践
压测隔离策略
隔离策略: 数据库隔离: 使用独立压测数据库或压测专用表 缓存隔离: 使用独立Redis实例 MQ隔离: 使用独立Topic 流量控制: 在网关层识别压测流量
常见问题处理
@Component
public class StressTestConfig {
// 动态调整连接池
@EventListener
public void handleStressStart(StressStartEvent event) {
// 调整线程池
threadPoolConfig.setCorePoolSize(event.getConcurrency());
threadPoolConfig.setMaxPoolSize(event.getConcurrency() * 2);
// 调整数据库连接池
hikariConfig.setMaximumPoolSize(event.getConcurrency());
// 调整MQ消费者并发数
rocketMQConsumer.setConsumeThreadNumber(event.getConcurrency());
}
// 压测结束恢复配置
@EventListener
public void handleStressEnd(StressStopEvent event) {
threadPoolConfig.restore();
hikariConfig.restore();
rocketMQConsumer.restore();
}
}
压测报告模板
# 压测报告 - 电商系统全链路压力测试 - 测试日期: 2024-01-15 - 测试环境: 生产环境3节点集群 - 压测工具: Apache JMeter 5.5 ## 测试结果 ### 总体指标 | 指标 | 结果 | |------|------| | 并发用户数 | 500 | | 最大TPS | 980.5 | | 平均响应时间 | 850ms | | P95响应时间 | 1.2s | | P99响应时间 | 2.1s | | 错误率 | 1.2% | ### 系统资源 | 服务 | CPU使用率 | 内存使用率 | 磁盘I/O | |------|-----------|------------|---------| | Gateway | 65% | 70% | 45% | | 用户服务 | 45% | 55% | 30% | | 订单服务 | 78% | 82% | 65% | | 库存服务 | 52% | 60% | 40% | ### 性能瓶颈分析 1. 订单服务MySQL连接池达到上限 2. 库存服务Redis读写偏高 3. Gateway线程数不足 ### 优化建议 1. 增加数据库连接池到200 2. Redis主从改为集群架构 3. Gateway增加线程池和灰度控制
完整压测代码仓库示例
project/
├── pom.xml
├── src/
│ └── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── config/
│ │ ├── filter/
│ │ └── monitor/
│ └── resources/
│ ├── application.yml
│ └── stress-test.jmx
├── stress-environment/
│ └── docker-compose.yml
└── scripts/
├── build.sh
├── deploy.sh
└── run-stress-test.sh
这个案例涵盖了Java全链路压测的完整流程,包括架构设计、代码实现、压测执行和监控分析,根据实际业务需求,可以调整具体的技术栈和配置。