`
yzabstract
  • 浏览: 14417 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

异步导入

阅读更多
在网上找了很多资料,导入五花八门。由于我参与到导入功能是从架构层面上做优化,解决大数据量,并发,耗时等性能问题。

我先出了方案文档如下

导出统一用异步实现提高用户体验,导出分页标准根据各自的也无需求定(全量导出不仅性能低,数据量特别大的情况下还会导致内存溢出)。


异步导出页面设计如下:
导出时间,表格名称,导出状态(导出中,导出完成,导出异常),导出进度条,操作(下载)

异步导出针对文件加密处理
点击导出,将文件上传到OSS,文件名加密规则为MD5(用户ID+创建时间)
点击下载进入统一下载接口,通过用户认证鉴权查询出用户ID,在通过下载ID查询出文件创建时间,读出文件流响应给前端。
针对导出进行加密处理,是为了防止获取到文件URL 随意进行下载。在安全性,保密性上面没有保障。

减少内存设计

不管是否大数据量,控制内存最多不超过10000条数据,集合存储获取到响应一万条数据,写入excel,清空list,在进行查询,在清空依次类推…… 最后上传至OSS。

定时任务

定时扫描用户导出表,超过一天的数据状态置为无效。

表设计如下

CREATE TABLE `user_export` (
  `id` bigint(20) unsigned NOT NULL AUTO_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) DEFAULT NULL COMMENT '创建时间',
  `create_by` bigint(20) DEFAULT NULL COMMENT '创建人',
  `update_time` bigint(15) DEFAULT NULL COMMENT '最后修改时间',
  `update_by` bigint(20) DEFAULT NULL COMMENT '最后修改人',
  `status` int(1) DEFAULT '0' COMMENT '0 有效  1无效',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户导出表';



针对大数据量内存溢出问题,改变了原有的导出模式,之前采用模板的形式进行导出。
现在用了Poi 导出,通过写入excel 清空List解决了大数据内存溢出问题,还提高了导出效率经过测试五万多条数据 三十秒左右导出完成。占用内存大小取决于配置项目export.size ,size的大小就是每次写入excel条数的大小也是list取值的大小。

针对上面每次写入excel会生成多个文件,采用了先将数据存入excel,在存入本地磁盘。最后统一上传到os 最后导出来是一个excel文件。


针对并发问题,采用了多线程ScheduledExecutorService类,类似于timer一样,现在配置的是五个线程,超过排队,线程执行间隔时间配置为一秒。

关键代码如下

创建book 对象
SXSSFWorkbook wbk = new SXSSFWorkbook(size);

String[] assetHeadTemp  为表头
String[] assetNameTemp 表头和 数据库映射数组
JsonConfig config = new JsonConfig();
            config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
            JSONArray jsonArray = JSONArray.fromObject(list,config);
sheet 生成表头
Sheet sh = asyncService.createSheetHeader(wbk,assetHeadTemp);


// 读取模板对象
            ByteArrayOutputStream byteOut = null;
            for (int i = 1; i <= num;i++){
//     asyncVo 该对象为异步实体对象   jsonArray 将响应结果集转成json数组方便后续赋值size  内存保存条数的大小 i 分页查询 第几页 ,i==num 判断是否为最后一页 wbk book对象  sh  excel  assetNameTemp 数据库表头映射 byteOut 每次分页输出流(因为是异步多线程 放入这里可以避多线程对象共享问题)         asyncService.asyncExport(asyncVo,jsonArray,size,i,i==num,wbk,sh,assetNameTemp,byteOut);
                if (num > 1 && i < num){
                    userLedgerDto.setPageIndex(i+1);
                    log.info(" AsyncServiceImpl AsyncExport 查询开始 num={} time={}",i,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS) );
                    list = getUserAllocation(userLedgerDto).getRows();
                    jsonArray = JSONArray.fromObject(list,config);
                    log.info(" AsyncServiceImpl AsyncExport 查询结束 num={} time={}",i ,DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS) );
                }
            }


public void asyncExport(AsyncVo asyncVo, JSONArray list, int pageSize, int pageIndex, boolean isLastRow, SXSSFWorkbook wbk, Sheet sh, String[] assetNameTemp, ByteArrayOutputStream byteOut) throws UnsupportedEncodingException {
        log.info(" start AsyncServiceImpl AsyncExport");
        // 创建jxsl对象
        String customerId = asyncVo.getCustomerId();
        String outName = URLEncoder.encode(MD5.encrypt(customerId + asyncVo.getCreateTime()), "utf-8") + ".xlsx";
        log.info(" AsyncServiceImpl AsyncExport 开始创建对象写入模板 - {}", DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));

        try {
            if (list.size() > 0) {
                for (int i = 0; i < list.size(); i++) {
                    Row row_value = sh.createRow((pageIndex - 1) * pageSize + i + 1);
                    // 遍历 jsonarray 数组,把每一个对象转成 json 对象
                    JSONObject job = list.getJSONObject(i);
                    // 得到 每个对象中的属性值
                    for (int k = 0; k < assetNameTemp.length; k++) {
                        Cell cellValue = row_value.createCell(k);
                        cellValue.setCellValue(job.get(assetNameTemp[k]) != null ? job.get(assetNameTemp[k]).toString() : "");
                    }
                }
            }
            list.clear(); // 每次存储len行,用完了将内容清空,以便内存可重复利用

            // 上传至OSS
            if (isLastRow) {
                byteOut = new ByteArrayOutputStream();
                wbk.write(byteOut);
                log.info(" AsyncServiceImpl AsyncExport 写入完成,开始上传至OSS - {}", DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
                byte[] buff = byteOut.toByteArray();
                InputStream input = new ByteArrayInputStream(buff);
                aliyunOSSUtil.putObject(classificationXLSX, folderSystem, outName, input, buff.length);
                log.info(" AsyncServiceImpl AsyncExport 上传完成,方法结束 - {}", DateUtils.formatNow(DateUtils.Pattern.YYYY_MM_DD_HH_MM_SS));
                // 在数据库中写入导出人 导出状态:导出成功
                asyncVo.setExportStatus("1");
                int i = asyncDao.updateUserExport(asyncVo);
                if (0 == i) {
                    log.info(" AsyncServiceImpl AsyncExport 导出成功,状态写入 - {}", "失败");
                }
                // 写入完毕才能释放流
                try {
                    byteOut.flush();
                    byteOut.close();
                    wbk.close();
                } catch (Exception e) {
                    log.info("AsyncServiceImpl 对象关闭失败", e);
                }

            }
        } catch (Exception e) {
            // 在数据库中写入导出人 导出文件名 导出时间 导出状态:导出异常
            asyncVo.setContent(e.toString());
            asyncVo.setExportStatus("2");
            asyncDao.updateUserExport(asyncVo);
            log.info("AsyncServiceImpl AsyncExport has error", e);
        }
    }




该方法是阿里云api自带 可供参考
/**
     * 上传文件到 OSS(不加密存储)
     *
     * @param classificationEnum 分类
     * @param folderEnum         文件夹
     * @param fileName           文件名称
     * @param fileLength         文件长度
     * @param inputStream        文件流
     * @return 文件名
     * @throws IOException
     */
    public String putObject(ClassificationEnum classificationEnum, FolderEnum folderEnum, String fileName, InputStream inputStream, long fileLength) throws IOException {
        createFolder(classificationEnum, folderEnum);
        //创建上传 Object 的 Metadata
        ObjectMetadata objectMetadata = new ObjectMetadata();
        //必须设置
        objectMetadata.setContentLength(fileLength);
        objectMetadata.setCacheControl("no-cache");
        objectMetadata.setHeader("Pragma", "no-cache");
        String fileNameExtension = fileName.substring(fileName.lastIndexOf("."));
        String contentType = 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();
        return fileName;
    }


















分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics