去年双11前夕,我们的淘客返利APP"省赚客"在进行大促预演时,遭遇了严重的性能瓶颈。当模拟5万并发用户访问时,核心下单接口的响应时间从平时的50ms直接飙升到3秒以上,错误率超过15%。这种情况如果发生在真实大促期间,将直接导致用户流失和佣金损失。
作为技术负责人,我立即组织团队开展全链路压测。我们面临三个核心挑战:
关键认知:全链路压测不是简单的接口测试,而是对系统在真实业务场景下承载能力的全面检验。必须模拟真实用户行为链路和数据特征。
我们设计了完整的用户旅程场景,覆盖以下关键路径:
每个场景都配置了不同的思考时间和并发比例。例如,搜索商品和浏览详情的并发比是3:1,模拟真实用户中"逛"和"买"的比例。
为了避免缓存命中率失真的问题,我们开发了动态数据生成器,核心逻辑包括:
java复制public class DataGenerator {
private static final Random random = new Random();
// 生成带权重分布的用户ID
public static long generateUserId() {
if(random.nextDouble() < 0.2) { // 20%概率生成VIP用户
return 50000 + random.nextInt(10000);
}
return 1 + random.nextInt(50000);
}
// 按类目分布生成商品ID
public static String generateItemId() {
double p = random.nextDouble();
if(p < 0.3) { // 美妆类
return "BEAU" + (1000 + random.nextInt(5000));
} else if(p < 0.55) { // 数码类
return "DIGI" + (2000 + random.nextInt(3000));
} else { // 服饰类
return "CLOT" + (3000 + random.nextInt(7000));
}
}
}
采用阶梯式增压模型,更精准地发现性能拐点:
在JMeter中通过Stepping Thread Group实现:
code复制Thread Group -> Ultimate Thread Group
Start Threads Count: 1000
Initial Delay: 0
Startup Time: 300
Hold Load For: 300
Shutdown Time: 60
压测到3万并发时,TPS曲线出现平台期,但CPU使用率只有40%。通过jstack抓取线程堆栈发现:
code复制"http-nio-8080-exec-125" #125 daemon prio=5 os_prio=0 tid=0x00007f8edc0f6800 nid=0x5e3f waiting on condition [0x00007f8e4a7e7000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c0064f50> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at com.zaxxer.hikari.pool.PoolBase.lockAndSuspendAcquisition(PoolBase.java:212)
at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:180)
关键发现:
解决方案:
properties复制spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.minimum-idle=20
spring.datasource.hikari.connection-timeout=3000
通过Arthas监控发现一条佣金统计SQL执行效率极低:
sql复制-- 优化前
SELECT SUM(commission) FROM t_order_detail
WHERE user_id = ? AND status = 'SETTLED' AND create_time BETWEEN ? AND ?;
-- 执行计划显示全表扫描
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra |
|----|-------------|---------------|------|---------------|------|---------|---------|-------------|
| 1 | SIMPLE | t_order_detail | ALL | NULL | NULL | NULL | 8736420 | Using where |
优化措施:
sql复制ALTER TABLE t_order_detail ADD INDEX idx_user_time (user_id, create_time);
优化后效果:
压测过程中发现部分Redis节点CPU达到100%,经排查是优惠券库存查询导致的热点Key:
code复制127.0.0.1:6379> hotkeys
1) "coupon:stock:618_100_2023"
2) "coupon:user:limit:618_100_2023"
解决方案:
java复制// 原始Key
String key = "coupon:stock:" + activityId;
// 分片Key
String shardKey = "coupon:stock:" + (activityId % 32) + ":" + activityId;
初始配置使用CMS收集器:
code复制-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
通过GC日志分析发现:
使用GCEasy.io分析发现老年代存在严重碎片:
切换到G1收集器并进行精细调优:
bash复制# 基础配置
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
# 关键调优参数
-XX:InitiatingHeapOccupancyPercent=45 # 降低并发标记触发阈值
-XX:G1ReservePercent=15 # 增加保留内存防晋升失败
-XX:G1HeapRegionSize=8m # 根据对象大小设置Region
-XX:ConcGCThreads=4 # 并发GC线程数
-XX:ParallelGCThreads=8 # 并行GC线程数
# 监控参数
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/var/log/gc.log
-XX:+HeapDumpOnOutOfMemoryError
调优后效果:
压测后期发现内存缓慢增长,通过MAT分析堆转储文件发现:

问题根源:
修复方案:
java复制@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.setConnectTimeout(Duration.ofSeconds(3))
.setReadTimeout(Duration.ofSeconds(5))
.build();
}
java复制CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 最大TPS | 3,500 | 12,000 | 342% |
| P99延迟 | 2,800ms | 180ms | 93% |
| 错误率 | 15% | 0.2% | 98% |
| GC停顿时间 | 1,200ms | 50ms | 95% |
连接池配置:
最大连接数 = (核心数 * 2) + 有效磁盘数JMeter使用技巧:
-Xmn参数调整JMeter自身堆内存,避免OOMGC调优原则:
MaxGCPauseMillis不是硬性保证,而是目标值这次压测让我们建立了完整的性能优化体系,后续又陆续应用到618、双12等大促备战中。最关键的收获是形成了"压测-定位-优化-验证"的闭环流程,这比单纯解决某个技术问题更有长期价值。