场景:
一个需求是需要处理一个txt文件中的数据,具体要求:
每个txt文件中数据上限是20W。
最开始的方式:
将txt中的记录逐条存入一个表中,然后遍历表中的记录,一条一条的处理,处理每一条记录时,大约需要查询6次数据库,插入6次数据库,更新3次数据库。
问题:
发现处理的速度比较慢,大概1秒中处理一条记录。
---------
需求要求提高效率。
考虑到是单线程处理,所以提速首先想到开多线程。
--------
改进方式1:
开启多线程分批处理数据,每个线程处理100条左右数据。
问题:
测试发现,当线程数少于4个时,每条数据的响应约为200毫秒,但是当线程数增多时,每条数据响应时间开始增大,5个线程单条响应500毫秒,7个以上线程响应时间1000毫秒以上。
所以不管开了多少线程,实际速度只增加到了5倍左右。
通过在程序运行时监控应用服务器性能未发现问题,监控数据库(数据库采用代理的mysql分库分表),发现数据库服务监控显示数据库每个节点的写IO都达到了97%以上(写入速度每秒钟1M),感觉因为数据库写IO能力太差,造成的请求都在写等待。
-------
分析:数据库的写IO造成了瓶颈,因为当前20w的数据,都是逐条处理,没条处理过程中,需要查询6次数据库,插入6次数据库,更新3次数据库,缺点就是:1,逐条连接数据库频繁开启、关闭事务,影响数据库性能。2,因为数据库的写IO能力很差,而每条记录处理时都要写6次数据库,并发时,数据库写io等待,而写IO等待,又可能会影响查询数据的速度,造成后面的记录处理时间都增大。
解决思路:在多线程处理数据的过程中,只逐条查询数据库,将所有的插入、更新操作都缓存到redis的list中,然后等所有的线程都处理完,再批量插入、更新数据库,这样就在代码中做到的了1:读写分离 2:改逐条开启事务插入数据为批量单线程插入数据。
--------
根据分析将代码修改后,发现问题:
虽然数据库的写IO占用百分比下降了,但是性能并没有提升,反而即使开一个线程,每条记录响应也在1000毫秒。
----------
事后分析:
因为项目分了两个服务部署,改进后是在服务1中根据对批量数取模的方式分配待处理数据,然后开启多线程,在多线程的每一批数据中逐条调用服务2(通过dubbo的rest服务通信)处理单条记录,通过日志发现,服务1发起调用服务2的请求后,在服务2中接收到请求需要1秒以上的延时,也就是说时间全部浪费在两个服务的相互调用上(而且每次调用都需要返回大量数据,这也可能造成速度慢),根据公司大拿分析,我们的dubbo服务提供配置文件中,每个接口都是用的rest协议,而不是dubbo协议,Rest协议是基于http的短链接协议,会产生大量的http time-wait.适应于对外提供服务时使用,dubbo协议为tcp的长链接协议。对外服务,使用rest协议,对内服务,使用dubbo协议。也就是说dobbo效率更高,所以我们应该将原来基于http的rest协议改成dubbo协议。
--------
改进方式2:
将dubbo服务的service.xml配置文件中原来的rest协议,改为dubbo协议;
修改业务逻辑,将原来的在服务1中开启多线程然后逐条调用服务2改为在服务1中开启多线程,然后将每个线程中的一批数据的获取方式传递给服务2,也就是改为一批数据用一个线程交给服务2进行处理,然后在服务2中一批数据循环处理,将要插入、更新数据库的数据直接扔到redis中,然后最后在所有线程都结束后,在服务1中从redis中取数据单线程批量插入数据库。
这样就将原来的20w数据需要两个服务直接调用20w次改为,每批1000条处理,只需要相互调用200次,大大减少了服务间的调用网络耗时,而且避免了传递大量参数。
最终测试,可以开到10个线程,每个线程的每条记录都在200毫秒的响应,也就是效率提高到了每秒钟30条。