前言
数据导出,这可以说是一个随处可见的需求,大部分管理平台,报表系统都会有这个需求。
对于这个需求,不少系统会做限制,只能从系统导出几千或几万的数据,再多的话就要提申请,经过层层审批,到 DB 那边的团队处理。
其实走不走申请,很大程度上是取决于公司的规章制度,大部分应该还是没有特别完善的,都是做在系统里,有权限的可以导出所有数据。
说实话,老黄也一直没搞懂,为什么有些人老是想着导出几十万,几百万的数据在那里看,过滤,筛选。。。
不过有需求,终究还是要满足的,像下面这种几百 MB 的 CSV 文件,是很经常看的见的。。。
常见问题
导出大文件时,一般都会遇到 带宽 和 内存 的问题。
带宽
如果说,供下载的文件,是放在我们的服务器上面,那么下载的时候是会占用我们的流出带宽。
这个在带宽比较小的情况下是很容易占满。
针对带宽问题,最好的办法就是不占用业务系统的带宽,这个需要引入第三方云存储,比如阿里云的 OSS,腾讯云的COS。这样提供的下载链接是云存储的链接,这样就和业务系统隔离了。
内存
在生成文件时,如果没有考虑到内存的情况,一次性把数据放到内存里面,就很容易占用大量的服务器内存。
不限制站点占用的内存,很容易影响服务器上面的其他站点。
限制了站点占用的内存,容易达到限制引发站点重启,从而影响正常的访问。
针对内存问题,就是避免一次性把数据全部都放在内存里面,可以分批处理。
下面再来看看一个具体的数据导出方案。
具体方案
这个方案会有有 5 个角色参与,用户,后台系统,中间件,导出系统和云存储。
大体如下图所示:
这里老黄把它粗略的分解成10个步骤。
1. 提交导出申请
用户想导出某些内容的时候,需要在后台系统里面提交申请。
2. 生成导出批次
后台系统接收到用户提交的申请后,给这个申请生成一个批次号,同时把导出什么内容,什么查询条件记录下来。
内容这一个可以存储查询的方法名,查询条件可以存储方法参数的 JSON 字符串。
这样在导出数据那一步时可以通过反射处理。
当然还少不了时间,人,状态这些基本信息了。
把这些信息入库,这一步就算 OK 了。
3. 发送导出批次到中间件
这一步涉及到中间件的选取问题,一般会建议选择 MQ 或 Redis。
发送的内容最简单的就是一个批次号就可以了,当然想把批次的其他信息组装一起发过去也是 OK 的。
4. 提交申请成功
当批次信息成功发送到中间件后,就可以认为系统已经接收了这个申请,这个时候就可以提示用户申请成功了。
5. 读取导出批次信息
导出系统这一块其实还是有很多设计的点的。导出系统这一块最好是能独立服务器部署,避免对应用服务器产生级联影响。
导出系统要监听中间件里面的批次信息,当收到批次信息后,它就要开始干活了。这个活分两类:
一类是,如果这个导出系统是 中心枢纽,只负责 调度 的话,它的活就是分配给具体的 worker 节点去执行后续的操作,好比创建一个 k8s 的任务。
另一类是,这个导出系统就是一个 worker 节点,就是负责执行后续内容的。
如果导出任务不是很频繁的话,导出系统 === worker 节点就可以了,也不要过度设计。
6. 查询/生成/加密文件
这一步才是真正的执行导出的操作。
有了批次信息就可以知道用户要导出什么东西,根据这个就去执行查询操作,然后把查询的结果生成对应格式的文件。
因为所有的文件,后面都是要上传到云存储,安全起见的话,需要加一个密码,避免所有人下载后都能打开。
文件有可能不是只生成一个,可能会按天按月切分,所以最好把文件放进压缩包里面。
这样在云存储上面都是带密码的压缩包。
7. 上传到云存储
生成好文件后,就需要把文件上传到云存储里面。这里建议有条件的要走内网去上传,不然 NAT 网关或服务器的带宽容易打满。
8. 组装下载地址
上传成功后要根据参数拼接好这个文件的下载地址。
拿到下载地址后,还需要进行有效期处理,即这个下载地址会包含着它的过期时间,什么时间之后就不能再访问了。相关云存储都提供了对应的方法,所以这一步会比较简单。
一般来说,一个文件会保留 3 ~ 7 天左右的有效期,更久的话就是一个月了。不排除有土豪公司永久保存。
设置有效期主要有几个考虑
业务上,不可避免在短时间内会有人导出相同内容,在有效期范围内可以复用这一个,避免重复生成。
资源上,云存储的价格,虽然不会特别贵,但还是要省着点,会定期清理一些不需要的历史文件
9. 回填信息
这一步其实就是把处理之后的下载地址、完成时间、批次状态等信息更新回批次信息里面
10. 查询导出申请并下载
到这一步之后,用户一般会收到站内信,告诉用户已经可以进行下载操作了,当用户点下载的时候,跳到云存储的地址,等待下载完成就可以了。
写在最后
数据导出,虽然说是一个相对不那么起眼的功能,但想做到比较好的体验,也是要花点心思弄一弄的。
这里介绍的这个方案是实际在用的了,不过只列出了比较粗的内容,还有一些细节内容就不再展开了,好比同一个用户连续导出相同的内容等等。