最近在重构以往文件导出的一部分代码,在重构之前我先摸索了了下,文件导出的整体链路以及实现方式,惊讶地发现,对于Saas平台系统而言,文件导出其实是一个普遍但考虑点偏多的功能。

  我们的业务属于电商ERP系统,平时用户存储的商品、订单、流水数量都是万级别单位,如何保证多数用户在导出数据文件的时候,系统内存不吃满,程序还能稳定运行,其实是一件不那么容易的事。其中实现,需要涉及到:对用户需求上的限制、导出文件的技术选型、分页数据查询、队列分组优先级、文件的上传和下载

一、需求限制:

  对于数据的导出,首先在源头上对用户导出的数据量做了限制,设置最大导出数量,禁止用户无限制导出,避免单个用户的导出操作一直占用服务资源,一般情况下,导出的最大数量因业务而异,比如订单最大的导出数量为10W条。

二、技术选型:

  目前采用的导出框架为阿里系的EasyExcel,在采用EasyEcel之前,用的是EasyPOI技术读写文件,但是考虑POI在读写文件的时候比较吃内存,假如服务并发量很小,其实使用POI是没什么问题的,但是对于Saas系统,并发上来后一定会OOM或者频繁的full gc 导致CPU飙高,给用户最直接的反馈就是页面点起来巨卡,并且目前POI还存一些在并发情况下未能修复的bug,综合考虑弃用EasyExcel投向EasyExcel,EasyExcel对于上述的问题都做了比较友好的处理,具体内容官网也给出了详细介绍:httpss://alibaba-easyexcel.github.io/support/about.html

三、分页查询:

  分页查询的目的其实非常明确,就是为了避免大数据查询占用过多内存导致OOM或者频繁FullGC,分页的大小建议范围为200-1000。在每次查询完一页数据,会将数据冲内存缓冲区flush到磁盘上。writer.flush();作用除了清楚内存Buffer之外,更重要的是将当前线程临时标记为空闲,允许调用程序在CPU资源分配给其他可能需要处理的线程。防止系统中几个大的导出任务阻塞住其他导出任务的现象。

四、队列分组优先级:

  虽然分页查询可以降低单个用户查询数据所占用的内存,但是当同时存在多个用户在导出的情况下,分页导出带来的效果其实是不大的,尤其是一些用户每到月末,财务需要对账,会大批量去导出数据,虽然分页上是降低了单个用户内存的占用,但是用户数量一多,内存还是容易出现吃满的情况,一般情况就是导出超时,最后失败。为了解决这个问题,需要把用户提交的导出任务丢到线程池队列里面,然后对队列进行分组区分出优先级别,对于一些VIP大户,我们会优先选择任务较少的队列。这边采用的队列为有序阻塞队列 LinkedBlockingQueue。通过线程池队列的形式去执行导出任务,可以避免大量的导出请求把服务压垮,简单来说就是对流量的一个削峰。

五、文件的上传和下载

  需要下载的文件一开始会以临时文件的形式存储在服务的本地磁盘上,当文件成功生成后,那么会将临时文件上传到远程的阿里OSS对象存储服务器,接着返回给前端OSS服务的文件下载链接,并删除临时文件。