一、开发分析

1、导出excel设计分析

  • 瓶颈:大量数据导出时

    • mysql查询连接占用时间过长
    • 组装对象生成excel时,容易导致CPU占用过高、JVM临时内存占用过大(可能导致oom,可能导致GC)
  • 设计:
    • 解耦Excel导出模块:通过加密api,暴露导出服务至内网
    • 限制每次导出数量:通过查询条件、阈值配置(数量阈值-待测)

2、设计实现(springBoot+mybatis+mysql)

【关键】

  • Excel导出方法

    • 采用org.apache.poi辅助jar包(3.14),组装生成excel流,直接附加到response字节流outputStream中返回
    • 为减小服务器压力,服务端不保存文件,直接返回流
  • 签名校验机制(弃用dubbo交互,采用http-api交互:因为dubbo传输需要将数据先序列化,导出数据较大,序列化传输性能不好;api则可直接传输流)
    • 客户端签名生成:

      • 先将约定密码进行md5加密后去后16位做key
      • 用key将当前时间戳timestamp,进行AES加密,得到32位签名sign(如7abf20cfb8c365c77c01e7904998444e)
      • 将生成签名附在header(key=”sign”)中
    • 服务端签名校验:(1、校验签名是否合法2、校验签名中时间戳是否过期)
      • 在拦截器Interceptor中进行校验
      • 获取Header中的签名sign,用约定密码解密,获得timestamp
      • 校验可否成功解密sign(保证签名合法)
      • 校验签名时间timestamp是否在限定时间内(保证一个签名过期作废,别人拿到也没用)
  • 多数据源(支持并行访问各db)
    • 每个数据源单独配置1套DataSource、SqlSessionFactory、DataSourceTransactionManager
    • 每个数据源单独搭配1套Mapper.java、Mapper.xml、MVC三层

【其他】

  • 调用端

    • 采用CloseableHttpClient实例,进行api调用
  • 参数沟通机制:
    • 附属在header中传递,设定key=”param”(相对于问号传值/路径传值,更简洁)
    • 通过Map格式传递(为防止中文乱码,在client端通过URLEncoder.encode做UTF-8编码,在sever端拦截器中通过URLDecoder.decode做UTF-8解码)
  • 浏览器端
    • 点击按钮,在新标签页打开下载窗口(临时生成target=_blank的a标签,然后点击,比window.open兼容性好)
    • 参数最后附加随机数(防止短间隔多次点击不进cotroller,无法响应)
    • 中文下载文件名兼容chrome、IE、firefox(在client端对header内中文文件名,当FF浏览器访问时做iso-8859-1编码,其他做UTF-8编码)
  • 可识别异常枚举:
    • statusCodeMap.put(“102”, “该api正在处理中”);
      statusCodeMap.put(“400”, “签名生成失败”);
      statusCodeMap.put(“403”, “查询条件不符合规定”);
      statusCodeMap.put(“404”, “找不到该api”);
      statusCodeMap.put(“406”, “签名校验失败”);
      statusCodeMap.put(“405”, “超过最大导出条数限制”);
      statusCodeMap.put(“408”, “请求中的签名超时”);
      statusCodeMap.put(“500”, “下载服务内部异常”);
      statusCodeMap.put(“503”, “下载服务api无法访问”);

3、小优化

a.返回处理状态
b.同个api并发访问/重复提交的处理(采用新开窗口)
c.文件名及下载方式兼容:chrome、IE、firefox,导出Excel后缀采用*.xls,以兼容win10、mac
d.监听下载进度(后续实现)
设计方案-1.对每个下载做唯一标识,开一个Map集合,记录每个下载进度;
2.在组装Excel循环中,定期更新Map集合中的进度,100%后remove该标识
3.单开异步请求(该处暂无方案),间歇访问进度集合,在进度条中显示

e.暂支持最大导出条数阈值(1W),支持不停服调整阈值
性能测试:本地导出1W条-5次:
cpu占用不超过50%
内存占用不超过300M

4、经验教训

  • InputStream转byte[]
public  static  final  byte [ ] input2byte ( InputStream inStream )  throws  IOException  {
ByteArrayOutputStream swapStream  =  new  ByteArrayOutputStream ( ) ;
byte [ ] buff  =  new  byte [ 100 ] ;
int rc  =  0 ;
while  ( (rc  = inStream. read (buff,  0100 ) )  >  0 )  {
swapStream. write (buff,  0, rc ) ;
}
byte [ ] in2b  = swapStream. toByteArray ( ) ;
return in2b ;
}
  • 读取字节输入流InputStream,填充字节输出流OutputStream
// 定义参数
OutputStream out  =  null ;
try  {
// 转移填充文件流
InputStream inputStream  = getResponse. getEntity ( ). getContent ( ) ;
out  = response. getOutputStream ( ) ; // 拿到HttpServletResponse输出流索引
out. write (IOUtil. input2byte (inputStream ) ) ; // 将查到的流,转写入外层响应流(此辅助方法引用上一点方法)
}  catch  ( Exception e )  {
log. error ( "##导出Excel-请求失败"  + e. getMessage ( ), e ) ;
}  finally  {
try  {
if  (out  !=  null )  {
out. flush ( ) ;
out. close ( ) ;
}
}  catch  ( IOException e )  {
log. warn ( "OutputStream关闭失败(一般为中途点击了取消)" ) ;
}
}
原因分析:多数据源报错异常
解决方案:在dataSource1方法上 添加@Primary注解,指定默认数据源,spring便不再报错
@Bean(name = "dataSource1", autowire = Autowire.BY_NAME)
@Primary
public DruidDataSource dataSource1() {
DruidDataSource ds = new DruidDataSource();
。。。
}
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘tool-db.xxx’ doesn’t exist
    ### The error may exist in file [D:\dev\workspace-mars\springBoot-mybatis\target\classes\com\demo\dao\mapper\xxx.xml]
    ### The error may involve defaultParameterMap
    ### The error occurred while setting parameters

原因分析:第二个数据源未能成功引用,导致SqlSessionFactory实例化时,dataSource2尚未扫入容器,导致找不找到数据库
解决方案:在方法sqlSessionFactoryBean2中,注入dataSource实例时,添加@Qualifier(ConfigParam.dataSource2) 注解,显示指定数据源实例

@Bean(name = ConfigParam.sqlSessionFactory2)
public SqlSessionFactory sqlSessionFactoryBean2(@Qualifier(ConfigParam.dataSource2) DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
  • Cannot resolve reference to bean ‘sqlSessionFactory’ while setting bean property ‘sqlSessionFactory’;
    nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
    Error creating bean with name ‘sqlSessionFactory’:
    Requested bean is currently in creation: Is there an unresolvable circular reference? 
原因分析:java config配置引起的bean相互引用日志报警告问题
@ConditionalOnClass({SqlSessionFactory.class,SqlSessionFactoryBean.class})
在使用单个mapper时没有报这个警告错误,但是当把mapper增加为2个以上时,就会报该异常信息,不知具体的原因。
跟踪代码,看到会优先执行MyBatisMapperScannerConfig扫描,虽然设置了AutoConfigureAfter,MyBatisMapperScannerConfig修改definition.setBeanClass(MapperFactoryBean.class)后,spring在做dataSourceInitializerPostProcessor处理时,会抛出该异常,不知道哪里出了问题。而且会重复出现2,3边同样的WARN,百分百重现。
解决方案:这个bug是mybatis-spring的,换到最新的1.2.4版本及以上就没问题了。
<dependency>
<groupId>org.mybatis </groupId>
<artifactId>mybatis-spring </artifactId>
<version>1.2.5 </version>
</dependency>

详细内容请直接到git讨论区:mybatis/spring#58

  • (ActionInterceptor.java:136) ERROR – java.lang.IllegalStateException: STREAM
    java.lang.IllegalStateException: STREAM
    at org.eclipse.jetty.server.Response.getWriter(Response.java:717)
    at org.apache.struts2.views.freemarker.FreemarkerResult.getWriter(FreemarkerResult.java:263)

原因排查:
这是web容器生成的servlet代码中有out.write(””),这个和JSP中调用的response.getOutputStream()产生冲突.
Servlet规范说明,不能既调用 response.getOutputStream(),又调用response.getWriter(),无论先调用哪一个,在调用第二个时候应会抛出 IllegalStateException,
因为在jsp中,out变量是通过response.getWriter得到的,在程序中既用了response.getOutputStream,又用了out变量,故出现以上错误。
解决方案:当处理成功是,return null;

  • Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property ‘urlPrefix’ of bean class [com.qz.tools.utils.exportclient.ClientProperties]: Bean property ‘urlPrefix’ is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
    at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:1044)
    at org.springframework.beans.BeanWrapperImpl.setPropertyValue(BeanWrapperImpl.java:904)

原因排查:在spring配置文件中,给对象静态变量注入值失败,低版本spring 不允许/不支持把值注入到静态变量中
解决方案:在不改变spring版本的情况下,可以利用非静态setter方法注入静态变量,即public void setUrlPrefix…….
or 升级spring版本

  • org.springframework.context.ApplicationContextException: Unable to start embedded container;
    nested exception is org.springframework.context.ApplicationContextException:
    Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
    at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133) ~

原因排查:springBoot引用内嵌tomcat时,配置了<scope>provided</scope>,去掉该scope即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

  • org.springframework.beans.factory.BeanCreationException:
    Error creating bean with name ‘entityManagerFactory’ defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]:
    Invocation of init method failed; nested exception is javax.persistence.PersistenceException:
    Unable to resolve persistence unit root URL
    Caused by: javax.persistence.PersistenceException: Unable to resolve persistence unit root URL
    Caused by: java.io.FileNotFoundException: class path resource [] cannot be resolved to URL because it does not exist

原因排查:未配置jpa,却在pom中引用了springboot-jpa的jar包
解决方案:去掉springboot-jpa包引用即可

  • JavaScript 获取当前时间戳

var timestamp=new Date().getTime();

  • FIREFOX 下载中文文件名出现乱码的java解决方案
if  ( "FF". equals (getBrowser (request ) ) )  { // 兼容火狐浏览器-保证中文名不乱码
fileName =  new  String ( "xxxFileName.xlsx". getBytes ( "UTF-8" )"iso-8859-1" ) ;
.......
}
// 服务器端判断客户端浏览器类型
private  static  String getBrowser (HttpServletRequest request )  {
String UserAgent  = request. getHeader ( "USER-AGENT" ). toLowerCase ( ) ;
if  (UserAgent  !=  null )  {
if  (UserAgent. indexOf ( "msie" )  >=  0 )
return  "IE" ;
if  (UserAgent. indexOf ( "firefox" )  >=  0 )
return  "FF" ;
if  (UserAgent. indexOf ( "safari" )  >=  0 )
return  "SF" ;
}
return  null ;
}
  • mysql循环插入1W数据,做导出压测

解决方案:新建mysql存储过程
步骤如下,在navicat中打开数据库test,[函数]->右键选择[过程]->[完成]
复制如下代码-[保存](其中insert换成自己的),名字取multiInsert->在[函数]中找到multiInsert右键->运行函数,坐等十来分钟左右就可以了

BEGIN
DECLARE i  INT  DEFAULT  1 ;
WHILE (i < 10000 )
DO
SET i =i + 1;
SET @mySql = 'INSERT INTO test(id,name) VALUES(1,2)';
PREPARE stmt  FROM @mySql;
EXECUTE stmt;
DEALLOCATE  PREPARE stmt;
END WHILE;
END;
  • JVM常用配置参数

简单来说堆就是Java代码可及的内存,是留给开发人员使用的;
非堆就是JVM留给自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、
每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。
堆内存分配 JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
(因此服务器一般设置-Xms、-Xmx相等以避免在每次GC 后调整堆的大小。 )
非堆内存分配 JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由-XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4
用于存储java永久生成对象(Permanate generation)如,class对象、方法对象这些可反射(reflective)对象
XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。
如果程序引用了大量的第三方jar,其大小超过了服务器jvm默认的大小,那么就会产生内存益出问题了。

-server 以服务模式启动jar包,启动比client慢,但可获得更高的运行性能
【堆内存配置】
-Xmx1024m:设置JVM最大可用内存为1024M(缺省值为)
-Xms1024m:设置JVM初始内存为1024M。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
【非堆内存配置】
-XX:MaxPermSize=128M :最小尺寸,初始分配
-XX:PermSize=128M :最大允许分配尺寸,按需分配
另外:如果有一个双核的CPU,也许可以尝试这个参数:-XX:+UseParallelGC 让GC可以更快的执行。

【解耦Excel导出服务】开发日志相关推荐

  1. VBA+批处理实现WORD转EXCEL数据分析开发日志

    VBA+批处理实现WORD转EXCEL数据分析开发日志 WORD程序转EXCEL并处理数据 项目目的 开发日志2020-09-01(批处理) 开发日志V1.2.1 2020-09-05 业务流程分以下 ...

  2. java 导入导出 插件_Java最优的Excel导入/导出工具开发,你用过吗?

    关注程序员7歌,一起用技术改变世界 在我们实际开发中经常会遇到Excel的导入与导出功能,而目前Excel操作工具也是数不甚数啊,但是7歌用过很多,还是觉得最近发现的tool-excel好用,让你实现 ...

  3. 开发指南专题十六:JEECG微云快速开发平台Excel导出

    开发指南专题十六:JEECG微云快速开发平台Excel导出 14.3.  Excel导出 导出工具类ExcelExportUtil 提供两个函数 //创建多个Sheet public static H ...

  4. 微服务开发利器:ELK进行日志采集以及统一处理

    微服务各个组件的相关实践会涉及到工具,本文将会介绍微服务日常开发的一些利器,这些工具帮助我们构建更加健壮的微服务系统,并帮助排查解决微服务系统中的问题与性能瓶颈等. 我们将重点介绍微服务架构中的日志收 ...

  5. Excel导出改造_只填写字典类型_就可以自动对应导出_字典类添加获取字典值方法---SpringCloud Alibaba_若依微服务框架改造---工作笔记013

    若依微服务框架,默认的excel导出功能,导出字段的时候需要: 在实体类上自己写上,比如 在属性上写上字典值,这里需要自己手动的写上 用readConverterExp写上. 但是如果,比如碰到,全国 ...

  6. 近期业务大量突增微服务性能优化总结-2.开发日志输出异常堆栈的过滤插件

    最近,业务增长的很迅猛,对于我们后台这块也是一个不小的挑战,这次遇到的核心业务接口的性能瓶颈,并不是单独的一个问题导致的,而是几个问题揉在一起:我们解决一个之后,发上线,之后发现还有另一个的性能瓶颈问 ...

  7. springboot+poi开发excel导出 加载Excel模板导出 Excel批量导出详解

    提到Excel导出功能,可能很多人都使用springmvc框架做过,笔者今天要给大家分享的是基于springBoot开发Excel复杂模板导出功能(所谓复杂模板指在模板里的特定表头里有不同的单元格合并 ...

  8. 国产Excel开发组件Spire.XLS【转换】教程(3):将 Excel 导出到 XML 和将 XML 导入到 Excel

    我们如何才能快速且高质量地将 Excel 导出到 Office Open XML 并将 Office Open XML 导入到 Excel?答案是大多数开发人员想知道和客户关心的问题.这里将演示一种将 ...

  9. easyexcel导出百万级数据_百万级别数据Excel导出优化

    这篇文章不是标题党,下文会通过一个仿真例子分析如何优化百万级别数据Excel导出. 笔者负责维护的一个数据查询和数据导出服务是一个相对远古的单点应用,在上一次云迁移之后扩展为双节点部署,但是发现了服务 ...

最新文章

  1. 构造函数不可以声明为虚函数,析构函数可以声明为虚函数
  2. bootstrap30-辅助类展示不同的背景颜色
  3. boost::math模块具有输出和输入方面以及字符串流的非有限示例
  4. php 怎么实现收藏功能,php收藏功能如何实现
  5. 参数 中_理解JavaScript中函数的参数
  6. 处女篇:ObjectDataSource+CodeSmith实现基础增删改查功能
  7. HDU 4607 Park Visit(树的直径)
  8. 算法笔记_031:计算中值和选择问题(Java)
  9. Python 这么热,运维要不要追赶 Python 的热潮?
  10. 短消息代理(cmpp20 smproxy)要怎么创建java项目_基于华为smproxy开发的cmpp3
  11. springSecurity分离资源服务器分析
  12. vba中FreezePanes(冻结窗格)用法
  13. NFT新玩法丨一文了解将NFT所有权分割成ERC20代币的Fractional协议
  14. 《第五项修炼》序列一
  15. 线性代数笔记8:矩阵的对角化
  16. C#事件中sender的小用法(转载)
  17. 码农:用git怕丢代码,每次都备份,同事:不学习的人真可怕!
  18. [TJOI2013]松鼠聚会
  19. 阿里云centOS7安装好Nginx设置外网可以访问80端口
  20. 中科蓝汛---长按3S进入语音助手功能实现

热门文章

  1. cos(a+b)=cosa*cosb-sina*sinb的推导过程
  2. 【Kotlin】告别KAPT,拥抱KSP API
  3. deepin20无法启动图形界面_U盘启动盘暗藏推广?用它来打造安全个性的PE工具箱...
  4. C#根据工作经验来谈谈面向对象
  5. 远程调用中间件RPC
  6. c语言作业报告,C语言程序设计综合作业报告——作业管理系统
  7. APS高级排程在钣金冲压行业的应用
  8. Python之有趣的小程序
  9. Linux下Oracle移植数据
  10. STC15 串口一 接收 发送 基本程序