5.1 JVM 与 GC 调优


5.1 JVM 与 GC 调优

本节摘要:Cassandra 运行在 JVM 上,MemTable 在堆内、Bloom/Key Cache 在堆外。4.x 推荐 G1GC,堆通常 4–8GB;一次 300ms+ STW 会让 Gossip 误判、CL 超时重试。SOURCE 5.1 强调 GC 停顿是分布式故障信令。

核心问题

阅读完本节,你应当能够:

  1. 解释 MemTable–Heap、Off-Heap、GC–Coordination 三份契约
  2. 配置 G1 的 MaxGCPauseMillis 与堆大小
  3. jstat/GC log 关联 WriteTimeoutException
  4. 对比 Mongo WiredTiger 堆管理与 HBase RegionServer GC

一、问题与直觉

监控上 Pending Compactions 正常,却突发 P99 写超时——翻 GC log 常见 Full GC 800ms。期间所有线程 STW,StorageProxy 认为副本超时,客户端重试 → 负载翻倍。MongoDB 同样怕大堆 Full GC;HBase RegionServer 常配 G1 + 较大堆但 MemStore flush 策略不同。Cassandra 因 高 churn 短生命周期对象(MemTable Cell),Young GC 频率本身就高。

二、核心原理

内存分层(SOURCE 5.1):

区域 内容 配置
堆内 MemTable、请求上下文 memtable_heap_space_in_mb
堆外 Bloom、Key Cache、CL buffer offheap_*
磁盘 CommitLog、SSTable 独立 SSD

G1 推荐jvm.options):

-XX:+UseG1GC -XX:MaxGCPauseMillis=300 -Xms8G -Xmx8G -XX:G1RSetUpdatingPauseTimePercent=5

堆 >8GB 时 G1 Region 扫描成本上升,Full GC 风险增——更大内存应加节点而非无限扩堆

关联症状

GC 现象 Cassandra 表现
Full GC 长 UN 抖动、Hint 堆积
Young GC 过频 写 P99 毛刺
堆 OOM 拒写、节点 down

三、工程实践要点

CMS 已废弃:4.x 勿用 CMS;JDK 11+ 验证 G1 默认参数。

容器:K8s limit 必须大于 heap + offheap + OS cache 预算;OOMKill 触发 hint 风暴。

诊断:启用 -Xlog:gc*;对照 nodetool tpstats MutationStage 阻塞。

⚠️ 常见坑:16GB 堆「一劳永逸」——G1 Full GC 停顿随堆线性恶化。

💡 关键直觉:调 JVM 就是调分布式超时语义——GC 日志与 CL 告警同一屏看。

一节小结

  • MemTable 在堆内 决定 Young GC 频率
  • G1 + 8GB 上限 是社区共识
  • Full GC 触发 Gossip/CL 级联故障
  • scale out 优于 scale up 堆
  • Mongo/HBase 同样需 GC 纪律,对象形态不同

下一节讨论批写、缓存与 Compaction 协同。

演练:GC 日志解读

# 启用 GC 日志(JDK 11+) -Xlog:gc*=info:file=/var/log/cassandra/gc.log:time,uptime # 定位 Full GC 与长停顿 grep -E "Full|Pause Full" /var/log/cassandra/gc.log | tail -20 # 用 GCViewer 等工具画停顿分布
GC 日志关键字段解读: [Full GC (System.gc()) ... 800ms] ← 全停顿,检查是否触发了 System.gc() [GC (Allocation Failure) ... 30ms] ← Young GC,频率高但单次短 [GC (Metadata GC Threshold) ...] ← 类元数据膨胀,检查代码类加载

诊断顺序:先确认停顿类型与频率,再对照同一时刻的 WriteTimeoutException 时间戳,如果高度重合,GC 停顿就是分布式超时的根因——这印证了「调 JVM 就是调分布式超时语义」。

jvm.options 完整示例

-Xms8G -Xmx8G -XX:+UseG1GC -XX:MaxGCPauseMillis=300 -XX:G1RSetUpdatingPauseTimePercent=5 -XX:+ParallelRefProcEnabled -XX:+DisableExplicitGC # 防止应用误调 System.gc() -Xlog:gc*=info:file=/var/log/cassandra/gc.log:time,uptime
# 应用配置后验证生效 jcmd <pid> VM.flags | grep -E "UseG1GC|MaxGCPauseMillis"

注意 DisableExplicitGC 双刃:它能防误触发的 Full GC,但也让需要显式 GC 的工具(如某些 Profiler)失效。生产建议开启,调试期可暂时关闭。MaxGCPauseMillis 是目标不是保证,G1 会尽力逼近,超限时要先看堆用量与对象分配速率。

容器内存预算

# Kubernetes 资源限制:heap + offheap + OS cache + 元数据余量 resources: requests: memory: 12Gi limits: memory: 16Gi # 8Gi heap + 4Gi offheap/OS + 4Gi 余量
# 容器内核对实际可用内存 free -h cat /sys/fs/cgroup/memory.max # cgroup v2

容器最危险的是「limit 卡在 heap 附近」:JVM 按 limit 申请元空间与堆外,OOMKiller 在堆还没耗尽时先杀进程,触发 hint 风暴与全环重试。预算原则:limit ≈ heap + offheap + OS cache + 20% 余量,并在启动脚本用 -Xms/-Xmx 显式固定堆,让 JVM 不随 limit 波动。

故障场景:Full GC 引发的连锁反应

时间线还原(典型的分布式故障链): T0 节点 A 触发 Full GC(停顿 1.2s) T1 Gossip 误判 A 为 DOWN(Phi 值超阈值) T2 写 CL=QUORUM 时 A 应答超时 → WriteTimeoutException T3 客户端重试 → 负载翻倍 T4 其他节点开始为 A 生成 hint,内存上涨 T5 GC 结束后 A 恢复,回放 hint,又一轮 I/O 峰值
# 事后诊断:用 GC 日志时间戳对齐 tpstats 丢包窗口 grep "Pause Full" /var/log/cassandra/gc.log | tail -5 nodetool tpstats | grep -i "WriteTimeout\|Dropped" # 两条时间线吻合,即可确认 GC 停顿是根因

防治组合拳:堆控制在 8GB 内、DisableExplicitGC 防误触发、MaxGCPauseMillis=300 约束目标、监控 GC 停顿超过 500ms 立即告警。MongoDB 与 HBase 同样有这类 JVM 连锁故障——这不是 Cassandra 独有,而是「JVM 分布式系统」的通病,只是 Cassandra 的 Gossip 与 CL 把停顿放大了。

监控与告警基线

# 生产必配的 GC 相关指标 jstat -gcutil <pid> 5000 # 实时 GC 利用率 grep "Pause Full" gc.log | wc -l # 24h Full GC 次数 # 告警阈值:Full GC > 3 次/小时,或单次停顿 > 500ms
指标 健康基线 告警
Young GC 频率 每秒数次 持续升高需查分配速率
Full GC 频率 0–3 次/天 超过即介入
单次 STW < 300ms 超过 500ms 立即查
Heap Used < 70% 超 85% 连续 5 分钟

调优闭环是「配置 → 观察 → 再配置」:每次改一个参数,用同一压测脚本对比基线,避免多变量混在一起无法归因。Cassandra 的 JVM 调优没有银弹参数,只有可观测的纪律。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U