JVM调优实战:日均百亿请求的网关服务的GC优化全记录

举报
江南清风起 发表于 2025/07/22 18:46:10 2025/07/22
【摘要】 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

策略:

  1. 短命对象在 Young 区被回收;
  2. 缓存进入 Old 区后保持稳定,避免反复晋升;
  3. 减少 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. 踩坑清单

  1. 容器内存限制未对齐-XX:+UseCGroupMemoryLimitForHeap 在 JDK17 已废弃,改为 -XX:+UseContainerSupport
  2. 大页内存未开启Transparent Huge Pages 与 G1 冲突,需显式关闭:
    echo never > /sys/kernel/mm/transparent_hugepage/enabled
  3. -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 的唯一途径;
  • 任何脱离压测和灰度的“一键模板”都是耍流氓。

image.png

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。