一次线上的GC问题排查

6.19号下午 线上系统出现了一次实时链路数据 不通畅的问题, 业务方反应更新的增量数据没有流入到HA3搜索集群
登录机器后检查日志后发现,在周六晚上到周天下午,cr_search_merge(机器人schema统一)表增量数据猛增,初步估计瞬间压在DRC-reader上的数据达百万级别的量
DRC-reader 应用会不间断的报 :

和 下面的错误:

这2个错误都是之前未曾出现过的错误

尝试重启应用后,隔几分钟后出现相同的错.

线上 DRC-reader 的原理如下图: 

原理:  程序从DRC 读取数据后, 经过不同步骤的数据处理后,通过多个管道不停的往后传输,最终达到输出器输出在不同的持久化介质上。输出器在把数据输出到介质时,会双写时间戳到ZK,应用重启时,从该时间戳的点位开始读,这样可以解决在应用重启时或机器崩溃时内存中的数据丢失的问题。

第一个错定位到日志打印处,分析代码后发现:当最后一个数据处理队列(上图中的 LBQueue writeRecords 队列)的容量达到限定的容量后,会抛出这个异常,并中断程序.

于是尝试把最后一个队列的限制长度调大,从1W调到100W , 重启应用第一个错不再出现。

但写ZK的错依然在报,再次调整配置参数: 

(1) 把ZK的写时间戳间隔加大,

(2)把ZK的启动时间戳改写到1个小时前. 

这个错也不再报了, 但输出器输出到持久化介质的速度非常慢,在经过几个小时的消化后,系统恢复正常,数据正常流转。

由于出问题时正值团队在外outing,只能通过调系统参数来规避这些问题。

周二回到公司后,尝试来重现当时的场景。 于是找了一台测试机器,把DRC的时间戳定位到一天前,把系统参数调回去,另外添加了些JVM DEBUG参数,把GC日志打印出来,保存系统崩溃时的内存快照。运行系统,果然问题复现了。

top 指令结果如下:

load升高,CPU飙升,系统卡死,同时应用日志报如下的错:

这个错很明显了,有GC问题,于是, 执行下面的命令,把内存dump下来分析
jmap -J-d64 -dump:format=b,file=dump.bin 23218

用 jvisualvm分析发现:

发现虽然最后的管道限制了长度为10000,但中间的管道消息数量堆积达到了25W,这些数据堆积在内存里,使得JVM频繁GC,占据了大量的CPU时间。

现在的架构使得,前面的DRC入口处并不知道后面管道的容量情况,无法对系统进行保护,不断的放入数据到系统,内存瞬间暴增,发生了GC问题。

数据流入系统的速度 比 系统输出数据的速度快,系统本身并未做限流措施,是本次GC问题的根本原因。

我做了个简单的统计(单机): 

    流入的速度 大概高峰期间每秒可达:  3500/QPS

    流出的速度: 最后一根管道输出器:  单线程的情况下,250QPS左右. 

                                                           开到三个线程并发输出: 400QPS左右

    这中间的差异会导致消息堆积,堆积在内存到一定量后必然发生GC问题,GC问题会进一步导致输出QPS降低,产生恶性循环。

解决方案:

在系统内部 和 外部进行限流。

内部: 在读取DRC数据入口处,通过接口拿到中间管道的队列长度,进行限流(目前限制1W)。

外部: 依赖外部的系统承受能力,和相关同学沟通,限流500左右.


做完这些工作后,再次运行系统,观察系统指标:

系统运行比较平稳了,上述的错误均未再出现。

后续会进一步提升输出器的并发能力,把这个提升上去后,可调高限流参数参数,使得整个系统的吞吐数据的能力提高



你可能感兴趣的:(java基础,JVM,并发编程)