现象,第一步
同步任务的http接口查询任务列表接口突然变卡,很久才相应,进入服务器后执行top命令发现CPU已经爆满。
top (查看进程占用资源)
机器是4核,所以占用了400%
top -H -p 276965 (查看进程ID下的子进程占用资源)
4个子线程,每个各占了100%
printf “%x\n” 276970
jstack 276965 | grep 439ea -A 100
通过查看当前线程执行的详情,发现输出的是gc线程
jmap -heap 276965
jmap的使用方式:blog.csdn.net/heihaozi/ar…
查看jvm当前的堆内存情况,老年代、新生代皆已满
上面的命令具体怎么用参考这篇blog:blog.csdn.net/cainiao1412…
第二步
既然是堆内存满了,此刻就要拿到整个堆内存的快照,执行
jmap -dump:live,format=b,file=heap.dump 276965
下载到本地使用 VisualVM 打开,有很亮眼的TableMapEventData,但是此时还是不清楚何处引起的泄露。
VisualVM的使用方式:blog.csdn.net/weixin_4546… , t.zoukankan.com/wangzun-p-1…
此时使用工具MAT继续打开dump,这里看就比较明显,是一个名为MySql…EventSource类中的tableMap…ByTableId的一个HashMap巨大,
MAT的使用方式:zhuanlan.zhihu.com/p/482860374 , blog.csdn.net/w2009211777…
第三步
根据MAT的提示,找到该处代码,可以看见在收到TABLE_MAP这个Event事件时,会将数据放到HashMap中
数据既然存,就也需要清理,在源码中只找到了一处清理该Map的地方,当收到ROTATE事件时,清理Map,那么由此可见如果不收到ROTATE事件,Map将会无限增长
什么是ROTATE事件?
参考:www.freesion.com/article/125…
也就是说我们的Map会等到binlog文件大小达到 max_binlog_size 的值时,才会进行清空,或者手动执行flush logs,但是生产环境中一般不会去手动执行,max_binlog_size 的值我们的库默认为 256MB。
什么是 Table Map Event?
意思就是 Table Map Event 会存储表的元数据,例如数据库名、表名、字段等,后续的Row Event会通过table id 和其关联,取到对应的元数据,因此这个存储是有必要的,
但是table id会随着缓存淘汰、刷新等机制出现变化,也就是说同一个库的同一个表的table id会不停的变化,因此table id并不是唯一的,所以我们的Map会随着程序的运行可能存储很多明明是同库同表,但是因为table id不同导致存map中很多重复的数据,导致占用内存越来越大。
解决方式
Debezium对mysql binlog同步默认使用的是 github.com/osheroff/my… , 这个库很久没有更新了,
但是Flink CDC使用的也是 Debezium,也就是说也依赖了这个库,所以可能也会存在这种问题,
两种解决方式,一是增大内存,均匀任务,让内存足够存储这么多的冗余数据,二是修改源码,在特定时机,例如新的table id过来时,将老的table id对应的数据清除?
针对binlog的同步,还有一个工具叫做maxwell,在生产环境中使用并没有出现这种问题,追踪了一下源码,也是类似的原理,但是maxwell使用我们使用了docker做资源隔离,每个进程给了2g,而Debezium任务则跑在同一个jvm中,因此更多倾向于均匀分配一些资源。
最终解决方式
我们的系统采用的版本是1.9.0.Final,在查看其源码时,在 MySqlStreamingChangeEventSource 类中,针对 ROTATE 事件发生时,并没有对map进行clear处理,所以导致了内存泄露,为了避免升级版本影响过大,我们采用了修改源码的方式,针对ROTATE发生时,对map进行清除。