type
status
date
slug
summary
tags
category
icon
password
文件导出
我们在业务中或多或少都接触过文件导出的需求,例如操作记录的流水导出、订单记录的导出或者指定时间段内的数据按照不同的维度统计之后进行导出等,功能虽然很常见但是想要做好文件导出,我认为需要充分地理解业务并且有较为广泛的技术知识,至少我认为应该做好如下方面:
- 产品设计合理化:明确导出数据的分类(直接导出或者统计计算后导出)、权限控制、导出数据量限制(可能影响用户体验,需要合理评估数据量)、
- 技术实现上的效率:导出速度、文件操作、幂等限制、查询优化(包括查询方式以及查询索引设置等)、内存参数设置、过期文件清理机制、导出请求异步化处理机制

导出操作常见的问题:
- 前端请求之后如果直接通过文件流的方式写入,当导出数据量很大的情况下会导致等待时间很长,用户很可能刷新页面重复请求,因此更好的方式是前端请求后短时间内不允许重复请求,后端异步处理文件写入,前端在单独的文件下载页面展示;异步化实现通常采用MQ的方式,
- GC问题:大数据量导出的情况下如果不分批次查询数据写入文件,会导致MySQL的内存被瞬间打满,JVM很可能出现内存溢出的问题,因此对于to B服务来说,当数据量可能很大的情况下,需要在产品设计和后端实现上同时优化:
- 导出数据量的预估:产品设计上需要控制导出数据的间隔时间以及单次最大导出量,单次导出无法满足用户需求的情况下,需要用户自己根据时间与数据量在前端分批次下载;这一设计的目的是为了能够从产品设计上减轻后端数据查询与导出的压力,同时也能够加快文件导出的速度,提升用户体验;还需要维护导出文件列表与记录查询功能,尽量避免多终端用户同时进行下载;
- 导出内容分类:需要确定用户导出内容属于流水类数据还是统计类数据,不同导出类型通常需要针对性的优化——流水类数据一般情况下近期数据会存储在当前服务数据库,历史数据可能会进行归档由其他业务部门维护;而统计类数据则可以通过统计表等方式实现,在导出时仅允许导出已经经过统计计算的数据
- 鉴权设计:产品设计上需要对用户权限进行鉴权,避免不符合要求的用户导出数据,同时针对下载请求可以设置验证码机制,当发起查询后需要导出数据时进行身份认证,也能够一定程度上限制导出次数,同时避免误触发;
- 导出频率控制:产品设计上还需要控制不同用户对于同一数据的下载请求间隔时间,避免误触发重复下载;
- 文件缓存与过期删除机制:对于用户下载的文件可能存在短时间内重复下载的需求(也可能多个客户端短时间内下载相同内容数据),导出文件后应该由后端进行存储,并应该在后端设置一定时间的文件时效,供用户重复下载(可以考虑CDN缓存、文件生成后向MQ中发送延迟消息,后台线程消费事件删除文件、后台线程轮询数据库记录并删除文件等)——说明直接由前端存储导致的性能下降的案例
- 分页渐进式查询与导出:后端实现上需要在每次导出操作之前计算本次的导出数据量,合理设置分页查询写入文件,避免单次查询数据量过大导致MySQL内存飙升,更重要的是需要避免单次大数据量查询导致的JVM内存溢出问题
- 同时也应该考虑文件写入的方式,尽可能保证写入速度
- 文件生成之后还需要考虑到文件的分布式存储,需要尽可能确保用户的文件下载速度,因此可以考虑使用CDN缓存或者MongoDB来存储文件;
- JVM参数的合理设置:导出操作会导致应用短时间内的GC频繁,因此需要考虑到JVM是否会由于大量导出操作导致内存溢出,对于导出业务频繁的应用可能需要考虑将文件导出分离服务,同时每次导出数据及查询数据时,基于MySQL从库查询,尽可能减少导出操作对于正常业务系统的影响
- 全局导出请求的最大限制:分布式系统中基于导出服务设置按照服务均衡分散导出请求(负载均衡策略),同时对于全局最大请求量进行限制
- 通过文件导出服务对于微服务的思考:文件导出服务通常是I/O密集型服务,当然其所带来的查询压力也会使内存占用升高,如果伴随着数据的实时统计,还会导致CPU占用增长,因此对于导出类型服务来说,对于物理资源的占用很高,如果全部在单个工程内实现,频繁的导出操作可能会导致应用的GC更加频繁,因此将导出类型服务独立为单独的服务不失为好的选择,可能避免导出服务的突发流量对正常业务造成影响。
- 作者:Run
- 链接:https://blog.geekerit.com/article/data-export
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。