1. 需求描述
数据导出成excel、csv等格式文件是一个非常常见的需求,普通的导出可能是直接请求后端接口获取数据然后生成对应的文件并执行下载,这种导出方式会存在很多问题,因此根据公司系统的导出总结一下新的导出流程。
2. 技术方案
流程图
传统导出流程图 & 异步导出流程图
流程分析:
1. 相比传统的数据导出,异步导出流程不需要考虑接口的响应时间,查询数据的速度也相对可控, 当然传统的数据查询也可以通过多线程去查询,那么这会造成不必要的CPU资源浪费。
2. 由前端获取数据生成文件执行结果不可控,无法做到重试。当导出的数据文件较大时,用户的电脑不一定支持住导出操作(在之前公司碰到过这种情况)。
3. 当其他用户在相同条件的查询下做导出时,可以根据任务里面的参数json做判断是否重复导出,如果是重复导出则直接复用原先的URL,当然一般情况下这个不太可行,每个用户的数据权限不一致,脱敏操作也不一样。
异步导出流程进阶
从上面的时序图可以看出,report-core(报表服务)的数据查询完全不受用户导出频率控制,bff服务也不会有太大的导出压力, DB层因为report-core读取的是slave库,master库是插入任务记录时用到,因此DB不会有压力。那么唯一压力来源于OSS业务线,当带宽不够时,文件上传会受影响,但这上传的时间造成的等待时间用户可能并不关心,因此服务性能不会有太大压力。
3. 技术实现
入库 & 异步部分伪代码
//rpc 接口伪代码
public Long exportTask() {
//第一步落库
insertTaskInfo(taskDO);
Long taskId = taskDO.getTaskId();
//第二步异步执行
executorService.sumbit(new ExportTask(taskHandler));
//第三步rpc接口返回taskId
return taskId;
}
//任务伪代码
public class ExportTask extends implements Runnable {
public ExportTaskHandler exportTaskHandler;
@Override
public void run() {
exportTaskHandler.handleExportTask();
}
}
//handler 伪代码
public interface ExportTaskHandler {
void handleExportTask();
}
//对应的具体任务执行方法
public class UserExportTask implements ExportTaskHandler {
@Override
public void handleExportTask() {
//从DB读取数据
File file = loadDataList();
//上传至OSS服务
uploadFile();
//更新Task文件URL
updateTaskUrl();
}
}
4. 总结
之前也是听很多同事在导出这块很头痛,大多是在抱怨前端导出虽然方便,但不够完善,主要问题在于用户的电脑导出大文件时经常浏览器崩溃(电脑CPU缓存不够), 因此总结出一个比较完善的导出流程,作为以后导出流程的一个借鉴。