大数据量导出的设计总结

背景

目前做的一个系统中有多个业务功能存在数据导出的功能,而且以对账明细导出为例,一般一个区县级税务机关下的某个属期的所有入库明细数据可能达到100多万,

由于数据量过大,且现有实现方式不合理,容易出现由于导出数据过多造成的“内存溢出”问题,并且由于现有导出是同步方式,处理时间相对较长,导致 WEBLOGIC 服务器监控到线程执行时间过长 从而产生 预警。

为了避免此类问题,现有实现加了个配置项,当导出数据量大于该配置项时,不允许导出,但实际使用场景中,有不少的用户需要导出大于该配置项的数据,此类需求现在无法满足。

分析

针对上述所列两个问题:

内存溢出

原因

原有导出文件方式,实现方式为采用JPA native sql 方式一次性从数据库获取所有数据,一次性将所有数据转换为DTO,一次性将所有DTO通过 POI组件 生成Excel。

这种方式下,由于生成文件过程中的所有对象都存在依赖关系,所以内存无法回收,随着数据量增多,非常容易出现内存溢出。

解决方法

采用分批方式,从读取数据,转换对象,到生产 excel , 使用分批 读取 分批 转换,分批生成的 方式,每一批处理完成,对象依赖关系即解除,对象即可释放,所以此种模式下不管读取多少数据量,不可回收内存占用几乎不变,不会随着数据的增多而不可回收内存线性增多。分批读取有如下几种方式:

  1. 通过rownum 分页
    即根据 rownum 第一批 获取 1-100条,第二批过去 101-200条。
    此方式有如下问题:
    1. 如果存在数据变动,此方式可能存在 不同页 有相同数据的情况, 比如先 获取 0-100的第一页数据,此时发生了数据更新,在最开头新增一条数据,当导出101-200的 第二页数据时,第101条数据和第一页数据相同。
    2. 另外,如果数据量过大,rownum 的模式存在 当分页越到后面,效率越低的问题。
  2. 通过业务字段分批查询
    通过业务字段分批查询,比如以入库明细为例,第一批导出编号为1到100的所有人的数据,第二批导出编号为101到200的所有人的数据。
    由于原本数据导出时会基于不同查询条件,组合条件较为复杂,所以直接转化为通过业务字段分批查询时 查询 效率会比较低下,并且难以通过增加索引方式提高效率。
  3. 一次性查询所有,通过ResultSet游标获取部分数据的方式
    此方式下,不再使用 JPA native SQL 方式,而是最原始 JDBC 游标处理方式,通过遍历 ResultSet 的方式,当遍历到一定数量后(比如100),转换为 DTO 并做后续的Excel生成。
    由于需要一次性查询,并且边查询边生成,在excel生成完成前,数据库连接一直没有释放。

鉴于前两种方式的存在的效率问题,使用第三种一次性查询的方式。由于数据库连接长时间不释放,超过防火墙 TCP 空闲限制时间会造成连接中断,需确保此种生成方式能在规定时间内完成查询(一般为1小时,查询到数据在一小时内即可,查询到数据,返回数据时 TCP 连接不会空闲,所以不会再断开)。

 

处理时间过长

原因

处理时间分为两部分,第一部分为查询,第二部分为处理,随数据量增大,查询和处理时间都会线性增长,处理时间不可控,必须转为异步处理模式。

解决方法

  1. 用户在前台查询后点击导出,根据数据量判断是否大于最大导出上限
    1. 如果小于等于上限,直接按原有模式,直接同步导出;
    2. 如果大于上限,提示文件在生成中,稍后去页面获取并下载;
  2. 定时任务处理导出任务,生成文件;
  3. 新增查询界面,页面显示文件生成进度,如果生成完成,提供下载链接 允许用户下载;
  4. 未避免服务器上文件堆积,定期清理文件,每天清理一次3个月前的数据;

 

你可能感兴趣的:(Java)