一、需求背景
接到需求是要对商家提供的数据文件解析为对应我们业务需要的对象,由于商家给的文件是zip文件,大小在10-100M直接,如果用单个线程处理解析势必会影响处理速度,
以下是我们解析时候写的代码。大概思路是这个样子:
1、技术方案一
-
解析zip文件:
-
- 由于zip文件中有对应自己要解析的流文件,解读流中指定的文件,获得对应的文件流。
- 单个线程测试读取流数据感觉还可以接受,处理时间大概在2000ms之内。
- 重点考虑多个线程去消费,需要将处理后的数据放在某一个队列中,让消费者去解析
-
生成对象列表:
-
- 处理解析出来的数据,生成我们需要的对象数据列表。
- 主要是消费文件解析出来的行数据。将数据处理为对应的对象。
- 将数据放入对应的列表中,这样在获取列表的数据进行后续的操作。
-
将对象持久化:
顺序读取列表中的数据,将解析出来的对象做持久化操作。对列表中的数据进行入库。
按照上边的分析,我们可能需要对几个地方可以进行多线程优化。按照这个思路就开干。
(1)处理代码
初期的消费代码是这个样子的。
启动测试后发现反射生成实例的时候出现以下情况,主要cpu的处理时间
(2)性能优化排查思路1
设置线程数量为2,CPU使用情况
cpu处理快照
cpu快照
线程的使用情况
执行后的处理时间为46164ms
将obj=(T)constructor.newInstance(line);对应的代码换成以new Object的方式。
(3)性能优化排查思路2
CPU线程树
线程快照
线程的使用情况
执行后的处理时间46937ms看起来没有什么差别
我们看到快照中有个细节。看到在list中添加数据时候的时间耗时挺长。我们看到操作list的数据为target = Lists.newCopyOnWriteArrayList();
该列表之前考虑是在处理数据时候多线程可以安全,有一个add的操作target.add(obj),我们将对应add的那个列表修改成为每个线程维护一个列表,再合并,相当于做了一个缓存。
CPU线程树
CPU快照没有抓到:
线程的使用情况:
执行后的处理时间4678ms直接影响了一个级别。
上边都是设计问题,
不要着急,还没有玩,你多执行几次就会知道上边的那个结果是有偶然性的,我们执行时候会执行到一半就会阻塞到哪里。一直排查不出什么问题。执行一次可以,两次可以,执行次数多了就会有问题。打印堆栈信息显示队列阻塞,但是线程还是不释放。在实际中这种问题最难排查,一个小的细节你可能就会引起大问题。
2、技术方案二
(1)处理代码
-
解析zip文件:
-
- 由于zip文件中有对应自己要解析的流文件,解读流中指定的文件,获得对应的文件流。
- 单个线程测试读取流数据感觉还可以接受,处理时间大概在2000ms之内。
- 重点考虑多个线程去消费,需要将处理后的数据放在某一个队列中,让消费者去解析
-
生成对象列表:
-
- 处理解析出来的数据,生成我们需要的对象数据列表。
- 主要是消费文件解析出来的行数据。将数据处理为对应的对象。
- 将数据放入对应的列表中,这样在获取列表的数据进行后续的操作。
-
将队列中的数据接入到对应的多个线程消费处理直接入库。不会在列表对象中存放。
可以看到我们在第二版的解析处理中少了对列表的生成。我们将不会对列表进行收集。直接拿来进行处理。
解析按照列表批量处理。还是单条数据处理。
思路2的代码没有记录。简单就是创建一个线程解析,多个线程消费处理。但是写的时候用的是CountDownLunch这个来控制是否已经都处理完成。但是在控制时候没有考虑到内存的泄漏问题。最后又在利辉老师的提议下整理了现在的版本。
(2)性能优化排查思路3
将zip解析后的数据直接交一个线程池处理。处理不会放入自己维护的队列。直接给到线程池的队列来维护对应的数据。核心的处理就在这里。
创建对应的线程池。
原来处理数据的任务没有变,创建了新的task作为处理的载体。
执行后的结果相当棒。
看看我们监控界面,相当完美啊。
对应的生产项目的处理监控CPU使用
原文连接
- https://blog.csdn.net/u013642886/article/details/79125760
- https://note.youdao.com/ynoteshare1/index.html?id=cbeee975a075fb40e212cd41f698f319