问题描述:
正常我们用框架生成excel 表的时候,导出表格很丝滑,没有什么问题。但是随着系统数据的增大。导出的数据越来越多,如果一次性load到内存中,根据垃圾回收机制,大对象可能直接进入老年代存储,所以很有可能会引发频繁的fullgc 问题。如果jvm 设置的内存太小的话,可能直接导致内存溢出的问题。所以当业务数据不断变多的过程,之前没有引发的性能问题也逐渐暴露出来。
今天就看看改造对应的easypoi 的导出。
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-base</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>cn.afterturn</groupId>
<artifactId>easypoi-annotation</artifactId>
<version>4.0.0</version>
</dependency>
改造过程:明确改造的问题点:
1.解决数据量过大导致,内存溢出问题
2.同步导出,由于网关对请求超时熔断,导致导出失败问题
针对第一个问题:
1.考虑批量的方式,分批导出数据库中的信息,通过重复指向不同的堆对象,及时释放内存,保证只占用一次循环的最大内存。
2.调用easypoi自带的导出大数据量的方法,分批导出后合并成一个sheet 导出。可参考easypoi大批量导出(二)
for (int i = 0; i < remoteCount; i++) {
//组装导出信息
List<ContractExcelVO> contractExcelVOS = new ArrayList<>();
int end = (i + 1) * sheetCount < contractIds.size() ? (i + 1) * sheetCount : contractIds.size();
List<Long> contractIdSubList = new ArrayList<>(contractIds.subList(i * sheetCount, end));
//组装你的表格主体内容
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
exportParams.setTitle("合同信息");
exportParams.setSheetName("合同信息_"+i);
//导出多个表格
workbook = ExcelExportUtil.exportBigExcel(exportParams, ContractExcelVO.class, contractExcelVOS);
}
第二个问题的解决思路:
1.设计一个导入任务记录表。在用户执行导出动作的时候就要插入一个任务。
2.导出的主体实现开启另外一个线程,采用异步导出。
3. 将生成的excel 文件上传到阿里云的oss 服务中,再将oss 返回的下载地址插入到任务记录表中,提供用户下载。
ByteArrayOutputStream bos = new ByteArrayOutputStream(); // 创建字节数组输出流
try {
workbook.write(bos); // 将Workbook写入字节数组输出流
} catch (IOException e) {
log.info("将Workbook写入字节数组输出流失败", e);
throw new RuntimeException(e);
}
byte[] bytes = bos.toByteArray(); // 将输出流转换为字节数组
InputStream inputStream = new ByteArrayInputStream(bytes); // 创建字节数组输入流
String uploadFileName = System.currentTimeMillis() + "contractInfo.xlsx";
String absoluteUrl = ossUtil.uploadOss(uploadFileName, inputStream);
if (StringUtils.isEmpty(absoluteUrl)) {
contractExportTaskInfo.setTaskStatus(TaskStatusEnum.FAIL.getIndex());
contractExportTaskInfo.setTaskDesc("上传文件到阿里云失败");
contractExportTaskInfoService.updateById(contractExportTaskInfo);
return;
}