JVM调优实战:日均百亿请求的网关服务的GC优化全记录
【摘要】 JVM调优实战:日均百亿请求的网关服务的GC优化全记录关键词:日均百亿请求、Spring Cloud Gateway、G1 GC、STW、内存模型、调参、压测、灰度 1. 业务背景与痛点 1.1 业务规模峰值 QPS:1.2 M(大促峰值)日均请求:≈ 100 B集群规模:物理机 120 台(32 C / 128 G),容器化混部,每台 8 Pod网关功能:统一鉴权、灰度路由、协议转换、限...
JVM调优实战:日均百亿请求的网关服务的GC优化全记录
关键词:日均百亿请求、Spring Cloud Gateway、G1 GC、STW、内存模型、调参、压测、灰度
1. 业务背景与痛点
1.1 业务规模
- 峰值 QPS:1.2 M(大促峰值)
- 日均请求:≈ 100 B
- 集群规模:物理机 120 台(32 C / 128 G),容器化混部,每台 8 Pod
- 网关功能:统一鉴权、灰度路由、协议转换、限流熔断。
1.2 初期现象
- Full GC:高峰期每小时 3 ~ 4 次,单次 STW 2.5 s
- P99 延迟:> 300 ms(SLA 100 ms)
- CPU 抖动:GC 回收线程占满 8 core,上游出现 502/504
2. 问题定位方法论
工具链 | 作用 |
---|---|
jstat -gcutil <pid> 1s |
实时监控各区使用率 |
jmap -histo <pid> |
快速查看 Top 对象 |
jmap -dump:format=b,file=xxx.hprof |
离线 MAT/YourKit 分析 |
GC-log (-Xlog:gc*=info:file=gc.log:time,uptime ) |
分析 GC 原因与耗时 |
Arthas dashboard/trace |
在线火焰图、方法耗时 |
3. 内存模型与对象特征分析
3.1 对象生命周期特征
- 短命对象:一次 HTTP 请求产生 200 KB 垃圾(Netty 堆外 + 堆内 DirectByteBuf → 转为 HeapByteBuf)
- 中寿对象:灰度规则缓存,存活 30 s ~ 5 min
- 长命对象:本地 Caffeine Cache(最大 4 GB,命中率 85%)
3.2 初始 JVM 参数(故障版本)
-Xms32g -Xmx32g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap # 容器感知
3.3 关键指标采样
采样点 | YGC 间隔 | YGC 暂停 | FGC 间隔 | FGC 暂停 | Old 区使用率 |
---|---|---|---|---|---|
12:00 | 2.1 s | 35 ms | 15 min | 2.5 s | 78 % |
20:00 | 1.6 s | 42 ms | 8 min | 2.8 s | 85 % |
结论:
- 老年代增长过快:大对象(缓存 Entry)提前晋升 → 触发 Evacuation Failure → Full GC。
- G1 默认停顿 200 ms 过松,导致并发周期来不及回收。
4. 调优目标与策略
指标 | 目标 | 现状 |
---|---|---|
P99 延迟 | ≤ 100 ms | 300 ms |
Full GC 频率 | 0 / 天 | 40 / 天 |
最大单次 STW | ≤ 200 ms | 2.5 s |
策略:
- 让短命对象在 Young 区被回收;
- 缓存进入 Old 区后保持稳定,避免反复晋升;
- 减少 Region 复制压力,防止 Evacuation Failure。
5. 参数逐版迭代
5.1 第一版:扩大 Young 区
-Xms32g -Xmx32g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:NewRatio=1 # Young=Old,≈16 g
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=6 # 晋升年龄
效果:
- Young GC 间隔 3.8 s,暂停 55 ms
- 但 Old 区仍上涨,触发 2 次 Full GC/天
5.2 第二版:精准控制 Region 大小
-XX:G1HeapRegionSize=16m # 默认 32 m → 降低大对象阈值
-XX:G1MixedGCCountTarget=16 # 并发周期后多批回收
-XX:G1OldCSetRegionThreshold=5
-XX:G1MixedGCLiveThresholdPercent=75
效果:
- Evacuation Failure 次数从 6 → 0
- Full GC 降为 1 次/天(手动触发)
5.3 第三版:优化缓存对象分布(代码级)
- Caffeine 配置
Caffeine.newBuilder()
.maximumWeight(4_000_000_000L) // 4 GB
.weigher((k, v) -> v.length)
.expireAfterWrite(Duration.ofMinutes(5))
.scheduler(Scheduler.systemScheduler()) // 异步淘汰
.recordStats()
.build();
- 对象池化:Netty
PooledByteBufAllocator
+-Dio.netty.allocator.type=pooled
5.4 第四版:灰度验证与回归
- A/B 集群:
- A 组:最终参数
- B 组:初始参数
- 指标 12 h 对比
| 指标 | A 组 | B 组 |
|—|---|—|
| P99 RT | 82 ms | 310 ms |
| Full GC | 0 | 18 |
| CPU idle | 42 % | 22 % |
6. 最终黄金参数(Linux Tuned)
# /opt/gateway/bin/start.sh
export JAVA_OPTS="
-server
-Xms31g -Xmx31g # 预留 1 g 给页缓存
-XX:+UseG1GC
-XX:MaxGCPauseMillis=80
-XX:InitiatingHeapOccupancyPercent=35
-XX:G1HeapRegionSize=16m
-XX:G1NewSizePercent=20 # Young min 6.2 g
-XX:G1MaxNewSizePercent=50 # Young max 15.5 g
-XX:MaxTenuringThreshold=3
-XX:G1MixedGCLiveThresholdPercent=80
-XX:G1OldCSetRegionThreshold=5
-XX:+UnlockExperimentalVMOptions
-XX:+UseStringDeduplication
-XX:+UseLargePages
-Dio.netty.allocator.type=pooled
-Dio.netty.recycler.maxCapacityPerThread=32768
-Xlog:gc*,gc+ref=debug,gc+phases=debug:file=/opt/logs/gc.log:time,uptime:filecount=10,filesize=100M
"
7. 代码级最佳实践
7.1 避免隐式大对象
// 反例:一次性分配 8 MB JSON
String json = objectMapper.writeValueAsString(list);
// 正例:流式输出
response.outputStream()
.write(objectMapper.writeValueAsBytes(list));
7.2 零拷贝转发
// Spring Cloud Gateway NettyWriteResponseFilter
NettyOutbound outbound = exchange.getResponse()
.send(request.receive()
.retain()
.map(factory::wrap));
7.3 监控埋点
@Bean
public MeterRegistryCustomizer<MeterRegistry> customizer() {
return registry -> registry.config()
.commonTags("application", "gateway")
.monitor(GCMetrics.getInstance());
}
8. 结果与收益
维度 | 调优前 | 调优后 |
---|---|---|
P99 延迟 | 300 ms | 82 ms |
Full GC | 40 / 天 | 0 / 天 |
峰值 CPU | 95 % | 60 % |
机器缩容 | — | 120 → 90 台 |
每年节省云成本 ≈ 180 万元。
9. 踩坑清单
- 容器内存限制未对齐:
-XX:+UseCGroupMemoryLimitForHeap
在 JDK17 已废弃,改为-XX:+UseContainerSupport
。 - 大页内存未开启:
Transparent Huge Pages
与 G1 冲突,需显式关闭:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
- -XX:MaxGCPauseMillis 调得过低:50 ms 以下会导致频繁并发周期,CPU 飙高。
10. 一键脚本(含回滚)
#!/bin/bash
set -e
APP=gateway
VERSION=${1:-latest}
IMAGE=harbor.xxx.com/gateway:${VERSION}
kubectl patch deploy ${APP} -p '{"spec":{"template":{"spec":{"containers":[{"name":"'${APP}'","env":[{"name":"JAVA_OPTS","value":"'${JAVA_OPTS}'"}]}]}}}}'
kubectl rollout status deploy/${APP}
# 如 5 min 内错误率>1% 自动回滚
kubectl wait --for=condition=available --timeout=300s deploy/${APP} || kubectl rollout undo deploy/${APP}
11. 结语
在高并发网关场景,GC 调优 ≈ 内存模型 + 代码实践 + 参数微调 + 持续验证。
本案例证明:
- 让对象在正确的代际结束生命周期,是消除 Full GC 的唯一途径;
- 任何脱离压测和灰度的“一键模板”都是耍流氓。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)