我们系统作为组织用户的数据源, 很多下级单位系统需要不定时的获取用户全量表的数据,因为安全的原因我们不提对外接口,而是将数据读取出来并生成csv文件,然后进行压缩为gz文件之后, 放在他们指定安全的ftp上然后他们下级单位的系统去ftp上读取文件进行解析录入到自己的系统
组织表有10万条数据, 用户表有70万数据 ,用户表记录当前用户的组织的id,这个我们需要转换为实际组织的编码,而且用户的附属信息在其他的表中我们需要连表查询
加了几天的班给弄完了,必须记录下来以后肯定会用到的
耗时: 30分钟左右
经过测试和分析,速度的瓶颈主要是查询数据库这个过程太耗时间了,大部分的实际都卡在ORM了, 在Navicat中查询很快,但是在代码中使用JPA查询非常慢,因为ORM架构涉及到数据映射和转换等各种处理, 可能你在Navicat查询1~2秒那么在代码中就可能需要几分钟 这个取决数据怎么传递的,过程是怎么解析,有没有缓存等,有很多因数需要实际去测试才行
我们服务器的cpu是6核的
在第一版的基础上,我们添加了多线程, 线程数为cpu核心*3每条线程查询的数据量为: 总数条数/cput核心*3
耗时: 5分钟左右
经过测试发现,会把服务弄堆溢出了 ,并且用户查询的时候有问题,比如我在代码执行的时候添加了几个用户那么是查询不出来的因为我们先求得总数量然后分配给每个线程查询,这个查询的数量在开始就固定下来了
排查为啥堆会溢出,我们通过工具发现, 一次性创建了18个线程每个线程查询4万左右的数据,那么同一时间相当于查询了70万数据到内存中,我们老年的的的大小是2.5G ,系统启动后大小时占用了老年的500mb左右, 启动跑批程序后,老年代一直拔高直到占满老年代, 因为对象还被引用中,那么是回收不掉的,所以gc尝试了几次后就GG了
然后我自己亲自计算了下大小,具体怎么计算可以百度下对象占用大小计算方式就知道了,我下面只是大概按照对象计算规则大致的字节估算了下,而实际的大小只会比这个大不会小
用户的数据: 908(字节) 大约一条数据的大小 , 700000条*912=638(MB)
组织的数据 380(字节) 大约一条数据的大小 , 100000条*384=38(MB)
在过程我们使用Map重新组装了组织的数据38*2=76(mb)
在过程我们使用List重新用户的的数据638*2=1,276(mb)
因为计算会有偏差那么我们可以得知在跑这个程序的过程中,在内存中常驻的内存大约1.3(G)~1.5(G) ,然后在算上过程中各种组件工具为了服务这么多数据所使用的对象,基本上就2G以上了,在加上系统本身就占用500~700左右,这就溢出了
如果想看真实动态堆大小变化可以使用监控工具或者jstat都行
那么有人会说我们可以调大点堆内存啊, 这个是不可取的因为数据量会越来越多,你不可能一直调整内存啊,如果数据量过千万了,那么你需要多大的内存才行?
在第二版的基础上我们进行了改良

核心线程和最大线程: 15 控制并发量-保证内存中最多同一时间只处理15万左右的数据量
队列: 1000 控制创建线程的峰值, 超出了就使用线程池默认的处理机制给丢弃任务

105~121 主要是获取配置的参数信息,比如ftp的ip账户和密码
115~117是将组织机构的信息查询出来,然后转换为id和code,便于之后用户可以通过id直接可以拿到对应的code,(hash是O(1)算法)
121~127是获取需要写的文件地址,如果存在了那么就删除

131~133 : 创建了线程通信的控制变量(乐观锁cas)
135: 死循环创建线程,每100毫秒创建一次
137~152: 每次查询1万用户,和用于控制创建线程的结束时机

153~171: 处理用户数据的转换
172: 计算总数
174~178: 将1万条处理完毕的数据,追加到文件的结尾,数据会压缩为gz (因为文件统一时间只能一次i/o那么不会产生线程安全的问题)

189~191: 判断所有真实的业务线程都处理完毕了
194~205: 给文件结尾追加描述信息 , 数据会压缩为gz
208 : 内部代码会将生成的csv文件推送给多个ftp
在测试环境(50多万用户数据和5万组织数据)的最终效果:

结果8498毫秒也就是只需要8秒就行了,在生产环境最多也就10秒左右
