1、背景:航班数据拆分,由原来的航班计划时间段拆分为每天的航班数据,未拆分的数据量400万,拆分为天的数据量1亿左右的数据。
2、部署到预发后进行数据拆分搬运之后的机器进程监控情况如下:
线程变化:
Old GC情况:
进入机器内部查看:
可以看到代码中的对象FlightScheduleSplitDO类创建的实例数852315,占用的内存空间204M,此实例一直在创建没有gc掉。CPU飙高,OOM。
3、原因如下:代码中逻辑,先从旧表中拿出200个待拆分的数据,然后丢到线程池中异步去执行,因为是while(true),所以会不停的从旧表中拿待拆分的数据放入线程队列中,在线程池中的线程先将旧表中的数据拆分,然后插入到新表中,在拆分的过程中不停的创建SplitDO对象,因为线程池中的方法没有执行完,所以此对象不能快速的回收,导致该对象越来越多,还有其中的list集合与队列大对象,
解决办法:将拆分的逻辑和插入数据库的逻辑提出一个方法,同时将线程池的逻辑也放到一个方法中执行。这样方法执行完之后,其中的对象可以被提前回收掉。
其中线程池的大小是25,然后将所有的所有的未拆分的数据以1800为一组,放到list中,然后将list分为25组,每组n个1800,然后由25个线程同时执行,每个线程执行n*1800个数据,省略了线程池队列的大小,然后每个数据进行拆分的时候都是方法去执行,方法执行结束后所创建的实例在新生代中被回收。最终执行的时间由10个小时缩短为1个小时。
总结:
1、通过top查看CPU情况,如果CPU比较高,则通过top -Hp <pid>命令查看当前进程的各个线程运行情况,找到占用CPU的线程PID,然后通过jstack命令 jstack 44 | grep "0x1b6" 打印pid为16进制的1b6的线程堆栈信息,查看该线程主要进行的工作。
2、如果是正常的用户线程,通过堆栈信息可以找到具体在代码的哪一行消耗CPU。
3、如果是VM Thread,则通过jstat命令查看gc情况,通过jmap查看哪些对象比较消耗内存。以上的情况就是有大对象消耗内存。
4、比较耗时的代码,可以加大请求量,查找多个线程同时堵塞的代码行数。
5、某个线程处于waiting状态时间长,可以查看20S-30S的线程堆栈信息,如果一个用户线程一直是waiting状态则需要排查。
6、死锁,jstack命令可以直接打印死锁的线程信息。
后面的三种情况主要针对导致功能缓慢的情况排查。