A person's character isn't determined by how he or she enjoys victory but rather how he or she endures defeat.
今天半夜手机上收到了一条报警短信,
某一台线上TASK机器CPU负载过高,我当时在想是不是又有同事在上面新部署了半夜凌晨的task,同时跑了太多任务,导致CPU负载太高,紧接着立马登上这台服务器,键入top先定位到具体的进程:
紧接着ps -ef | grep 500558 :
发现是tagtask CPU占用太多导致,那么CPU占用太高主要可能有两个方面:
1.任务计算量太大
2.full gc次数频繁
首先看了下这个工程的代码,并没有发现有什么计算量,复杂度特别高的地方。
紧接着通过 jstat -gcutil | grep 500558 500 30 查看了下gc情况发现full gc 次数非常的频繁,老年代回收效率非常的低:
再通过jmap -histo:live 500558 | more查看内存中主要有哪些对象占用了大量内存,这个步骤当时没有保存截图。
接下来还是通过 jmap -dump:live, format=b,file=tagtask.hprof 500558,将dump文件下载到本地,通过IBM HeapAnalyzer 这个工具查看存活对象的引用信息:
官网下载工具,然后通过java命令启动程序,打开下载下来的dump文件:
,发现有大量的char、4个Object类型的list占用了绝大部分的jvm内存。
根据引用树,查看代码,排查问题:
1.task中主要逻辑是通过一次性读取hive表数据,发送kafka消息,这个步骤中,由于读取速度远大于producer的生成速度,导致大量的消息堆积在本地没有发送出去。
2.有4个队列,每次先从hive中读取所有数据再内存里,然后再发送消息,这样就容易导致占用大量jvm内存无法释放:
问题查出来了,那么接下来就需要我们想办法去优化这个task来解决这个事情:
思路:
现在是通过一个while循环,全量拿到hive数据,再通过这个list来处理数据,那么可不可以将list调整成一个阻塞队列,while里一边存,处理的线程一边消费呢?
这样的好处时,内存不会堆积,并且从阻塞队列里拿出数据处理,该对象会很快就会被垃圾回收掉。不会一直占有大量内存。
改进之后的代码如下:
首先定义好一些公共变量,一个线程池,用来管理读取hive子线程,2个map一个存储的是生成结束的标识,一个存储的是不同业务的处理队列。countdownlatch是其他的优化点,不用太关注哈。
取HIVE数据这一块:
在拿到resultRet后,会组装好一个对象放入到全局的阻塞队列中,并且在这里我做了一个简单的限流,保证每放入8000个会休息一秒钟。
发送kafka消息这一块:
通过全局的公共原子变量来判断hive表数据是否导入完成,然后在循环里处理阻塞队列里的数据,并且每4000条数据才批量发送一次kafka消息。
优化之后的cpu以及gc情况得到了非常明显的改善,如下图所示:
gc次数没有当时截图,老年代增长的速度非常缓慢,full gc恢复正常,新生代gc4秒一次,也很正常。