springboot+easypoi大数据Excel导出

刚开始以为这个很简单,但是做的时候却遇到很多坑。特此记录一下,整理一下自己的解决方法,希望也可以帮助到其他人,如有不合理的地方,请望指出,不胜感激。
第一个坑:数据量过大,使用数据库查询(还是已分表的多表查询)拿取太慢了
这个很耗时,之前我以为通过数据优化的方式就可以解决,太年轻,数据量庞大,它的偏移量就是那么大,所以耗时还是无法解决。没办法,只能借助其他工具,因为我的导出数据是日志数据,即它不怎么变化,所以我使用了mongodb去存储我的数据。OK,数据拿取很快了。
第二个坑:内存泄漏
这个坑我是做之前就知道会存在的,毕竟那么大的数据量是很占服务器资源的,单个进程任务的数据量过大,而又无法及时回收到系统内存,最终导致系统内存耗尽而宕机。easypoi和mongodb都有相应的处理。
mongodb可以通过分页的方式去拿取数据,可以控制每次拿取的数据量:
嘿嘿,都分页了,所以这里还可以用多线程去做哦。

// pageSize 自定义取出大小
public Workbook excelWorkBook(Query query,Integer pageNum){
		//分页拿取数据
		Pageable pageable = new PageRequest(pageNum,pageSize);
		query.with(pageable);
		List<Test> dataList = mongoTemplate.find(query,Test.class,collectionName);
		if (!CollectionUtils.isEmpty(dataList)){
			workbook =  ExcelUtil.exportBigExcel(params, Test.class, dataList);
		}
		logger.info("第"+pageNum+ "页的"+"的真实数据量大小为:"+ dataList.size());
		if (!pageSize.equals(dataList.size())){
			pageNum++;
			workbook = excelWorkBook(query,pageNum);
		}
		return workbook;
	}
	
public static Workbook exportBigExcel(ExportParams entity, Class<?> pojoClass, Collection<?> dataSet) {
		//批量处理数据
        ExcelBatchExportService batchService = ExcelBatchExportService.getExcelBatchExportService(entity, pojoClass);
        return batchService.appendData(dataSet);
    }	

第三个坑:网关时间限制
哎,我这个服务是走了网关的,网关一直报超时,我当时就想把这个时间限制关了,但是这样做未免太不明智了,还好柳暗花明又一村,既然超时那我就骗过网关就好啦,自然而然,异步就是一个很好的解决方式。bingo,springboot还自带异步处理@Async,这篇博客里的讲的很不错,可以看下这个>https://www.cnblogs.com/huanzi-qch/p/11231041.html具体去实现。
很好很好,超时问题解决了,但是前端怎么拿到我的文件呢。因为这个服务已经用到了阿里云的OSS,所以我这里用的是OSS,也可以暂时放在服务器上。当后台把文件导出处理好了,并且上传到了服务器,可以通过websocket或者接口轮询的方式去通知前端可以去服务器上下载文件了。

// 上传
public void handleWorkBook(Workbook workbook,String fileName){
	InputStream fileContent = null;
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			workbook.write(bos);
			byte[] barray = bos.toByteArray();
			fileContent = new ByteArrayInputStream(barray);
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			//我这里是通过OSS存储文件的,用redis存储这个文件的过期时间
			ossManageUtil.uploadFile(fileContent,fileName);
		} catch (Exception e) {
				e.printStackTrace();
		}		
}

这个文件是一次性的,可以通过redis的监视器去删除文件,要不然浪费存储空间资源。

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener  {
	@Autowired
	private OSSManageUtil ossManageUtil;

	public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
		super(listenerContainer);
	}
	@Override
	public void onMessage(Message message, byte[] pattern) {
		String expiredKey = message.toString();
		if(expiredKey.startsWith("ExportExcel_")) {
			String key = expiredKey.replace("ExportExcel_", "");
			try {
				ossManageUtil.deleteFile(key);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

还有很多优化的点,几百万的数据量可以分文件再压缩或者多sheet导出,可以用多线程加快导出速度,可以用任务队列来控制这个并发等等。这些实现的过程可能又会遇到很多坑哦,哈哈。

你可能感兴趣的:(学习,java,spring,mysql,redis,mongodb)