`
yzabstract
  • 浏览: 14957 次
  • 性别: 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;
    }


















分享到:
评论

相关推荐

    阿里异步导入导出框架源码

    阿里异步导入导出框架是阿里巴巴提供的一种高效的数据导入导出解决方案,主要用于处理大数据量的导入和导出操作。此框架的设计目标是提高系统性能,减少阻塞主线程,使得在处理大量数据时,系统仍能保持良好的响应...

    Excel文件的导入(异步线程)

    本知识点主要探讨如何高效地实现Excel文件的导入,特别是利用异步线程技术来提升性能。异步线程在多任务环境中,能够使得程序在等待IO操作完成时,不会阻塞主线程,从而提高用户体验。 首先,我们需要了解Excel文件...

    前端大数据导入,异步处理

    总结来说,前端大数据导入与异步处理涉及的技术点包括:File API的使用、文件分块读取、异步编程(Promise、async/await)、Ajax或fetch通信、数据预处理、Web Workers以及前后端的安全防护。理解并掌握这些技术,能...

    3816异步导入提升机

    "3816异步导入提升机"这个标题可能是指一个特定的项目或工具,它与JavaScript语言中的异步导入机制有关,用于提升(hoisting)异步模块加载的效率。在JavaScript中,异步加载通常涉及到动态导入(dynamic import)和...

    Excel_To_DB:一款将Excel表格中的数据导入至数据库中的小工具,SpringBoot+EasyPoi+Redis消息队列实现Excel批量异步导入数据库

    4. **异步导入**: 异步处理机制是通过将数据导入操作从主线程中分离出来,让主线程可以立即返回,而数据导入工作在后台线程中进行。这种方式可以提升用户体验,因为用户不需要等待整个导入过程完成就可以继续其他...

    AsImpL:Unity的异步导入器和运行时加载器

    AsImpL(OBJ)Unity的异步导入器和运行时加载器需要Unity 5.5.4或更高版本。 在运行时和在编辑器模式下,将3D模型(当前仅OBJ )加载到Unity场景中,并将它们作为资产导入到unity项目中。 为了利用在运行时加载器中...

    excel导入大数据失败解决方法

    然而,当面临大数据量时,Excel的导入功能可能会遇到瓶颈,导致数据无法成功导入。本篇文章将详细探讨“Excel导入大数据失败”的问题及其解决方案,重点关注Java环境下如何高效地处理大数据。 首先,我们要理解为...

    directory-import:模块同步或异步导入(需要)您指定文件夹中的所有模块

    模块将允许您从指定的文件夹中同步或异步导入(需要)所有模块。 您可以使用返回对象中的模块,也可以按文件调用函数 安装 npm i directory-import 安装后,您可以要求模块和导入文件: const importDir = ...

    POI实现Excel导入导出并附带加载进度条

    本项目通过Apache POI实现了Excel的导入与导出功能,并在操作过程中加入了进度条展示,增强了用户体验。 首先,Apache POI的核心类HSSFWorkbook和XSSFWorkbook分别用于处理老版本的.xls和新版本的.xlsx文件。在导入...

    SpringMvc+POI 导入Excel

    接着,我们引入Jquery.form.js这个JavaScript插件,它可以实现异步(Ajax)方式的文件上传。这种方式下,用户无需等待整个文件上传完成就能看到页面反馈,提高了用户体验。在前端,我们需要配置jQuery的form插件,并...

    protobuf导入定义(import)

    在大型项目中,可能有很多.proto文件,为了避免一次性加载所有文件导致的性能问题,某些实现提供了异步导入或懒加载机制。这使得只有在真正使用到导入的类型时,才会解析对应的.proto文件。 ### 5. 循环导入 需要...

    导入数据源代码

    五、异步导入 在大型数据集导入时,为提高用户体验,可以采用异步编程。利用async/await关键字,将导入过程放在后台执行,不阻塞UI线程。 ```csharp async Task ImportDataAsync() { // 创建并打开连接,执行导入...

    Mysql数据库导入超出默认大小和默认执行时间的解决方法

    8. **使用异步导入**:如果系统允许,可以考虑使用异步方式导入数据,即在后台线程进行导入,不影响前端应用的正常运行。 9. **调整innodb_buffer_pool_size**:此参数控制InnoDB存储引擎缓存的数据量。适当增大该...

    2021 全年工作日假期周末mysql快速导入表

    此外,对于大型企业,可能还需要考虑数据导入对在线服务的影响,选择在低峰时段进行,或者使用异步导入的方式。 总的来说,这个主题涵盖了MySQL数据库管理、SQL脚本使用、Java JDBC技术以及数据预处理等多个方面,...

    excel文件导入mysql

    同时,为了提高性能,可能需要分批处理大文件,或者采用异步导入的方式,以免阻塞用户界面。 总结起来,"excel文件导入mysql"这个主题涵盖了从文件读取、数据处理到Web开发和数据库操作等多个方面,涉及的技术栈...

    vue+element实现大文件上传

    vue+element实现大文件上传

    C#连接操作MySQL数据库进行添加、修改、删除、查询、批量导入、异步处理等操作的演示代码

    鉴于MySQL数据库的流行与强大,决定多学习...同时实现了数据库查询,BindingSource绑定控件、BindingNavigator导航控件绑定,批量数据使用BackgroundWorking异步导入功能,及进度条动态显示数据库批量操作进度等功能。

    PythonVMJaspy.zip

    特性:支持多线程集成调试器,并支持远程交互、调试(CLI, PyCharm, …)灵活的预处理器架构,便于Jaspy优化支持原生扩展模块(time, dom, …)完全支持的元类、子类和内置操作符重载基于BigInteger.js,支持异步导入和...

Global site tag (gtag.js) - Google Analytics