八股文之JVM 调优:一次 Logstash 性能问题排查记录

本文记录了我在一个真实业务场景中踩过的一个坑:Logstash 同步 MySQL 到 Elasticsearch 越跑越卡,最终发现是 JVM GC 造成的问题。

起因是为了优化查询,我们引入了 ES;结果同步慢了,又引入了 Logstash;结果 Logstash 也慢了......从此踏上了排查之路。希望这篇文章能帮到在类似场景挣扎的你。

背景:分库分表 + 搜索优化 = ES 出场

我们业务用的是 MySQL + ShardingJDBC 做的分库分表,一开始挺好用的,读写都能扛。

但随着业务发展,查询变得越来越复杂,尤其是那种跨库模糊查、聚合查、排序查,ShardingJDBC 就开始吃力了。

内心OS:这个shardingjdbc真的越来越多坑~

于是我们决定:上 Elasticsearch,搞个同步方案,用它来支撑复杂的查询场景。


引入 Logstash:同步 MySQL 到 ES

要把 MySQL 的数据实时同步到 ES,我们一开始调研了几个方案:

  • Canal:对 binlog 做解析,但改动大;
  • Debezium:需要 Kafka 支撑;
  • DataX:适合离线批量;
  • Logstash:简单直接,用 JDBC 插件就能搞定。

于是我们选了 Logstash + JDBC + Elasticsearch output 的方案:

bash 复制代码
input {
  jdbc {
    jdbc_connection_string => "jdbc:mysql://xxx"
    jdbc_user => "root"
    statement => "SELECT * FROM table WHERE updated_at > :sql_last_value"
    schedule => "* * * * *"
  }
}
output {
  elasticsearch {
    hosts => ["http://localhost:9200"]
    index => "my_index"
  }
}

一开始运行非常顺利,基本上延迟控制在秒级,业务方反馈也很满意。


问题来了:Logstash 越跑越卡

Logstash 跑了几天后,同步开始变得 越来越慢

起初是几分钟的延迟,后来变成十几分钟,最后接近实时不同步,导致 ES 中数据滞后,严重影响业务查询。

我们检查了:

  • MySQL 正常,没什么大查询;
  • ES 也没瓶颈;
  • 机器资源还有富余;
  • Logstash 本身没报错,但 CPU 一直很高。

于是开始怀疑是 JVM 内存问题


提高内存?没用!

我们试着把 Logstash 的 jvm.options 中的堆内存调大:

bash 复制代码
-Xms12g
-Xmx12g

想着是不是内存不够、老 GC 导致抖动。

结果并没有什么用,该卡还是卡。于是,我们转向更底层的方向:GC 分析


jstat 一查:Full GC 爆炸!

我们使用 jstat -gc <pid> 1000 采样,看到非常惊人的 GC 数据:

bash 复制代码
YGC   YGCT    FGC     FGCT
4047  234s   19406   16351s
  • Young GC 只发生了 4000 多次,总共 4 分钟;
  • Full GC 居然有 1.9 万次,耗时超过 4.5 小时!

这时候我们意识到:卡顿的根源在于 JVM 的 GC,准确说是 Full GC 太频繁了。


看看 GC 策略:居然还在用 CMS?

翻看了 Logstash 默认的 JVM 配置:

ruby 复制代码
bash
复制编辑
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly

也就是说,它用的是 CMS 垃圾回收器

而 CMS 的特点是:

  • 老年代不压缩;
  • 遇到碎片化或内存不足时就会频繁 Full GC;
  • 一旦 Full GC 多,性能直接雪崩。

难怪跑几天后就崩了:随着数据增多、对象生命周期拉长、老年代对象堆积,GC 开始崩坏式触发。


小结一下当前发现的问题

说明
问题现象 Logstash 越跑越慢,延迟越来越大
表面现象 CPU 高、内存不满、无报错
本质原因 Full GC 次数远远超过 YGC,消耗大量时间
根本诱因 CMS GC 不适合长期运行、大内存场景,内存碎片无法整理

解决方案:切换 G1 GC,效果立竿见影!

经过分析,我们决定抛弃 CMS,改用 G1 GC ,它专门为 大堆内存、低延迟场景 优化,非常适合 Logstash 这种长时间运行的服务。

最终 JVM 参数如下:

bash 复制代码
# 设置堆大小(固定,避免动态扩展)
-Xms20g
-Xmx20g

# 使用 G1 GC(替代 CMS,适合大内存)
-XX:+UseG1GC

# 控制 GC 暂停时间(单位:毫秒,可根据业务调节)
-XX:MaxGCPauseMillis=200

# 更早触发 GC,避免老年代满
-XX:InitiatingHeapOccupancyPercent=45

# 启用并行处理引用,提升 GC 效率
-XX:+ParallelRefProcEnabled

# 增加 GC 日志(可用于后期调优)
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-Xloggc:/var/log/logstash/gc.log

🔥 调整后效果非常明显:

  • 已连续运行 48 小时,无 Full GC;
  • CPU 降至 30% 左右;
  • 延迟回到秒级;
  • GC 日志正常,Young GC 占主导。

总结

以前背了这么多JVM的八股文,没想到终于派上用场了,不过之前看一些更细的还要分新生代跟老年代的比例之类的。我们是大力出奇迹型

这次问题排查过程中,我有几个感悟:

✅ CMS 已经老了,G1 是更好的替代

Logstash 默认用 CMS 已不适合现代高负载场景,建议直接切换到 G1 GC

✅ GC 日志要打开!

开启 -Xloggc 日志,从一开始就观察内存与回收行为,可以极大提升排查效率。

✅ 日志中没有报错 ≠ 没有问题

GC 卡顿不会报错,但会拖慢整体吞吐,这是"隐性"的大杀器。

相关推荐
MonKingWD14 小时前
【JVM】万字总结GC垃圾回收
jvm
重庆小透明15 小时前
【从零学习JVM|第二篇】字节码文件
java·jvm·学习
黄雪超16 小时前
JVM——类加载的流程与机制
开发语言·jvm·python
LiuYaoheng1 天前
【JVM】Java类加载机制
java·jvm·笔记·学习
黄雪超1 天前
JVM——JVM中的字节码:解码Java跨平台的核心引擎
java·开发语言·jvm
数据潜水员1 天前
C#基础语法
java·jvm·算法
懋学的前端攻城狮2 天前
深入浅出JVM-02自动内存管理机制全面剖析
java·jvm·后端
ob熔天使——武3 天前
Java进阶---JVM
java·开发语言·jvm
杀神lwz3 天前
JVM学习(七)--JVM性能监控
jvm·学习