最近产品要求实现一个大数据量生产文件并提供下载的功能,重点是避免OOM并且尽可能的快。

1 设计思路

a 考虑OOM上,考虑系统并发情况,很简单的做法就是处理请求时,将业务逻辑放到线程池中执行。

b 其次一个web系统,对于客户端的请求要考虑响应时间,不能时间过长。

结合以上两点考虑可采用异步处理方案,将请求放入线程池中异步执行,然后响应本次请求,并提供查询异步任务完成情况的接口,最后再提供接口来提供用户希望得到的东西。

这样便有三个接口:请求下载文件、查询文件信息、下载文件

以上是总体设计思路,考虑到具体的业务场景再对其进行优化。

考虑到用户体验情况,在请求后对于数据查询并最终生成文件的工程进行同步等待,等待一定时间后若是任务尚未完成,则相应请求,前端提示生成文件任务未完成;若等待后任务已完成,则相应信息中带有关键的请求信息,前端将此请求用于调用下载文件接口直接下载文件。

以上是总体设计思路,一般来说生成的文件需要记录相关信息,并展示给前台,可以考虑建立库表,或者直接通过本地磁盘对文件夹以及文件的名字来保存信息,这里推荐使用库表,扩展性更强。

2 优化过程

前文内容对于普通需求已经能够实现,但是对于大数据量的需求还需优化。

2.2 查询优化

对于一个excel每一页行数都是有限,具体数量可百度,并且如果查询数据量非常大,一次全查出再生产文件是有oom风险,所以查询是分区段查询。

这里实际上有两种方案:

a 对各个区段的查询并发执行,使用limit等语句,视具体数据库而定。

b 查询完一个区段再查下一个区段,但是查询方式是通过索引字段,大小方向查询。

两种方案各有优劣,为避免oom本身线程池有限,实际上b方案对于每一个异步查询任务效率更加高,在并发请求导出任务量高的时候会更有优势;若是此任务请求量少,在短时间内只有一个请求,那么a方案会更快的完成任务。本人的工作中采用的是b方案。

对于分区段查询首先要明确的是必须有唯一排序字段排序才能进行分区段查询。因为数据库再不排序下取数据是无序,并且大数据下很高可能数据顺序是不一致,则分区段查询多次查询时,会出现少数据、多数据的情况。

很多文章都有提到分页查询时候,分页数量越多,查询越慢,因为一般的分页查询sql写法,也是将查询指定的偏移量的数据之前的数据都扫描过一遍,再从指定偏移量之后的数据取数。本人工作用的数据库是ignite,比如要查询1001-1010的数据,在执行计划中可看到是全查出了0-1010的数据,再去1001-1010的数据。

因为分页查询这部分内容很多文章都有描述,就不过多展开,直接讲优化方案:

首先上文提到分区段查询必须要有唯一字段排序,假如我这有一个查询通过empno编号排序(编号为数字,没有正负符号),那么第一次查询1000条数据即可成:

 empno > '-1' limit 1000

此时取查询出的数据最后一个empno值,假设为‘123456’,那么查1001-2000条的数据便可写成:

empno>'123456' limit 1000

只到某次查询无数据为止,实际上经过本人在ignite中的测试,随着查询次数的增多,查询数据会越来越快,前提是必须将排序字段加上索引,这是基本操作。
上述只是单个唯一排序字段的做法,实际上很多情况下是没有一个唯一字段,而是多个字段组合成唯一。比如本人工作中主键就是组合主键empno、year、month。

这种时候排序就得写成order by empno、year、month,那么对于唯一值就行分大小区段查询就很难做了,本人想到的解决方案如下:

empno >= ? and year > = ? and month > ?,多个排序,非最后排序字段值>=(或<=),最后一个排序字段值>(或<),大家可以思考一下,这么写其实是和排序相符的做法。比较少见多字段组合唯一值的分页查询优化方案,这里分享给大家。

最后还有一个要点,排序应当和索引字段顺序保持一致,索引字段顺序的选择,应当不同值越多的字段放在越前面,这样综合起来性能更高,假设以上若是排序字段首位放month字段,可能数据库自身优化方案中都不会使用索引查询,这部分也有很多相关文章有描述,这里就不在深入。

 

待续