异步导入
在网上找了很多资料,导入五花八门。由于我参与到导入功能是从架构层面上做优化,解决大数据量,并发,耗时等性能问题。
我先出了方案文档如下
导出统一用异步实现提高用户体验,导出分页标准根据各自的也无需求定(全量导出不仅性能低,数据量特别大的情况下还会导致内存溢出)。
异步导出页面设计如下:
导出时间,表格名称,导出状态(导出中,导出完成,导出异常),导出进度条,操作(下载)
异步导出针对文件加密处理
点击导出,将文件上传到OSS,文件名加密规则为MD5(用户ID+创建时间)
点击下载进入统一下载接口,通过用户认证鉴权查询出用户ID,在通过下载ID查询出文件创建时间,读出文件流响应给前端。
针对导出进行加密处理,是为了防止获取到文件URL随意进行下载。在安全性,保密性上面没有保障。
减少内存设计
不管是否大数据量,控制内存最多不超过10000条数据,集合存储获取到响应一万条数据,写入excel,清空list,在进行查询,在清空依次类推……最后上传至OSS。
定时任务
定时扫描用户导出表,超过一天的数据状态置为无效。
表设计如下
CREATETABLE`user_export`(
`id`bigint(20)unsignedNOTNULLAUTO_INCREMENT,
`module_name`varchar(50)DEFAULT''COMMENT'模块名称',
`tab_name`varchar(50)DEFAULT''COMMENT'表格名称',
`export_status`int(1)DEFAULT'0'COMMENT'0导出中1导出完成2导出异常',
`content`varchar(1000)DEFAULT''COMMENT'异常描述信息',
`create_time`bigint(13)DEFAULTNULLCOMMENT'创建时间',
`create_by`bigint(20)DEFAULTNULLCOMMENT'创建人',
`update_time`bigint(15)DEFAULTNULLCOMMENT'最后修改时间',
`update_by`bigint(20)DEFAULTNULLCOMMENT'最后修改人',
`status`int(1)DEFAULT'0'COMMENT'0有效1无效',
PRIMARYKEY(`id`)
)ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='用户导出表';
针对大数据量内存溢出问题,改变了原有的导出模式,之前采用模板的形式进行导出。
现在用了Poi导出,通过写入excel清空List解决了大数据内存溢出问题,还提高了导出效率经过测试五万多条数据三十秒左右导出完成。占用内存大小取决于配置项目export.size,size的大小就是每次写入excel条数的大小也是list取值的大小。
针对上面每次写入excel会生成多个文件,采用了先将数据存入excel,在存入本地磁盘。最后统一上传到os最后导出来是一个excel文件。
针对并发问题,采用了多线程ScheduledExecutorService类,类似于timer一样,现在配置的是五个线程,超过排队,线程执行间隔时间配置为一秒。
关键代码如下
创建book对象
SXSSFWorkbookwbk=newSXSSFWorkbook(size);
String[]assetHeadTemp为表头
String[]assetNameTemp表头和数据库映射数组
JsonConfigconfig=newJsonConfig();
config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
JSONArrayjsonArray=JSONArray.fromObject(list,config);
sheet生成表头
Sheetsh=asyncService.createSheetHeader(wbk,assetHeadTemp);
//读取模板对象
ByteArrayOutputStreambyteOut=null;
for(inti=1;i<=num;i++){
//asyncVo该对象为异步实体对象jsonArray将响应结果集转成json数组方便后续赋值size内存保存条数的大小i分页查询第几页,i==num判断是否为最后一页wbkbook对象shexcelassetNameTemp数据库表头映射byteOut每次分页输出流(因为是异步多线程放入这里可以避多线程对象共享问题)asyncService.asyncExport(asyncVo,jsonArray,size,i,i==num,wbk,sh,assetNameTemp,byteOut);
if(num>1&&i<num){
userLedgerDto.setPageIndex(i+1);
log.info("AsyncServiceImplAsyncExport查询开始num={}time={}",i,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
list=getUserAllocation(userLedgerDto).getRows();
jsonArray=JSONArray.fromObject(list,config);
log.info("AsyncServiceImplAsyncExport查询结束num={}time={}",i,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
}
}
publicvoidasyncExport(AsyncVoasyncVo,JSONArraylist,intpageSize,intpageIndex,booleanisLastRow,SXSSFWorkbookwbk,Sheetsh,String[]assetNameTemp,ByteArrayOutputStreambyteOut)throwsUnsupportedEncodingException{
log.info("startAsyncServiceImplAsyncExport");
//创建jxsl对象
StringcustomerId=asyncVo.getCustomerId();
StringoutName=URLEncoder.encode(MD5.encrypt(customerId+asyncVo.getCreateTime()),"utf-8")+".xlsx";
log.info("AsyncServiceImplAsyncExport开始创建对象写入模板-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
try{
if(list.size()>0){
for(inti=0;i<list.size();i++){
Rowrow_value=sh.createRow((pageIndex-1)*pageSize+i+1);
//遍历jsonarray数组,把每一个对象转成json对象
JSONObjectjob=list.getJSONObject(i);
//得到每个对象中的属性值
for(intk=0;k<assetNameTemp.length;k++){
CellcellValue=row_value.createCell(k);
cellValue.setCellValue(job.get(assetNameTemp[k])!=null?job.get(assetNameTemp[k]).toString():"");
}
}
}
list.clear();//每次存储len行,用完了将内容清空,以便内存可重复利用
//上传至OSS
if(isLastRow){
byteOut=newByteArrayOutputStream();
wbk.write(byteOut);
log.info("AsyncServiceImplAsyncExport写入完成,开始上传至OSS-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
byte[]buff=byteOut.toByteArray();
InputStreaminput=newByteArrayInputStream(buff);
aliyunOSSUtil.putObject(classificationXLSX,folderSystem,outName,input,buff.length);
log.info("AsyncServiceImplAsyncExport上传完成,方法结束-{}",DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
//在数据库中写入导出人导出状态:导出成功
asyncVo.setExportStatus("1");
inti=asyncDao.updateUserExport(asyncVo);
if(0==i){
log.info("AsyncServiceImplAsyncExport导出成功,状态写入-{}","失败");
}
//写入完毕才能释放流
try{
byteOut.flush();
byteOut.close();
wbk.close();
}catch(Exceptione){
log.info("AsyncServiceImpl对象关闭失败",e);
}
}
}catch(Exceptione){
//在数据库中写入导出人导出文件名导出时间导出状态:导出异常
asyncVo.setContent(e.toString());
asyncVo.setExportStatus("2");
asyncDao.updateUserExport(asyncVo);
log.info("AsyncServiceImplAsyncExporthaserror",e);
}
}
该方法是阿里云api自带可供参考
/**
*上传文件到OSS(不加密存储)
*
*@paramclassificationEnum分类
*@paramfolderEnum文件夹
*@paramfileName文件名称
*@paramfileLength文件长度
*@paraminputStream文件流
*@return文件名
*@throwsIOException
*/
publicStringputObject(ClassificationEnumclassificationEnum,FolderEnumfolderEnum,StringfileName,InputStreaminputStream,longfileLength)throwsIOException{
createFolder(classificationEnum,folderEnum);
//创建上传Object的Metadata
ObjectMetadataobjectMetadata=newObjectMetadata();
//必须设置
objectMetadata.setContentLength(fileLength);
objectMetadata.setCacheControl("no-cache");
objectMetadata.setHeader("Pragma","no-cache");
StringfileNameExtension=fileName.substring(fileName.lastIndexOf("."));
StringcontentType=getContentType(fileNameExtension);
objectMetadata.setContentType(contentType);
objectMetadata.setContentDisposition("inline;filename="+fileName);
//上传Object
ossClient.putObject(bucket,StringUtils.join(classificationEnum.getPath(),folderEnum.getPath(),fileName),inputStream,objectMetadata);
inputStream.close();
returnfileName;
}