性能优化之Excel导出大数据CPU资源过高的解决方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kity9420/article/details/86624578

在最近的一个项目上,遇到一个很奇葩的问题,应用无法访问或响应速度非常的慢,查看服务器资源发现CPU占用将近99%,经过多次分析,发现原来是进行Excel大数据导出导致的,问题找到后,立马寻找解决方案,当前的几个解决方案详细如下
一、将导出Excel改成CSV格式
分析:Excel的格式比CSV格式要复杂的多,CSV是纯文本的字符串,没有复杂的计算逻辑,而Excel中有各种的汇总计算,合并单元格,冻结行,冻结列等等,因此生成Excel要比生成CSV要慢的多,但是经过与客户的再三商讨后,客户不同意导出CSV格式,依然要导出Escel,因此这个解决方案被否决了。

二、将一个Excel拆分成多份导出
分析:因为导出小文件的时候,不会存在资源耗尽的问题,因此想到将一个大Excel拆分成多个导出,同时按拆分Excel的个数启动多线程,每一个线程导出一份Excel,这样可以避免导出一个大文件的问题,但是客户觉得这样导出完成后还要进行多个Excel数据的汇总,操作上很不方便,因此也不同意这个解决方案

三、将报表导出与主应用分离
分析:考虑到即使导出失败也不会影响到主应用的使用,且报表个数很多,因此有必要将报表单独出一个应用,独立部署,和主应用分离,这样也方便后续针对报表做对应的横向扩展,从架构方面考虑,这个方案用户接收

单单将报表与主应用分离,还远远不够,依然会出现耗尽资源的问题,因此还需要继续向下分析

四、修改Tomcat内存参数
分析:我们知道,Tomcat的bin目录下,是有很多运行相关的文件,其中就包括运行参数的设置,有一个文件catalina.sh(window:catalina.bat),可以在这个文件中配置tomcat的运行内存,最大内存等,在文件的最开始,加上如下的配置

export JAVA_OPTS="-Xms2048m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=1024m"

在这里插入图片描述
配置项说明:
-Xms:初始堆大小,一般为物理机内存的1/4
-Xmx:最大堆大小,初始化大小一般与-Xms相同
PermSize:JVM启动时初始化Perm的内存大小
MaxPermSize:最大可占用的Perm内存大小,在生产环境上一般将这两个值设为相同

详细的内存参数配置,可参考这篇文章https://wyj-study.iteye.com/blog/2254845

五、程序优化
5.1 API优化
这部分的优化,是从另一位前辈那里得来的,主要是修改创建Workbook,Sheet页等相关API,几个非常重要的API优化前后对比,在优化前

XSSFWorkbook wb = new XSSFWorkbook(); //创建工作溥
XSSFSheet sheet = wb.createSheet(element.attributeValue(("name")));//创建Sheet页
XSSFRow headRow = sheet.createRow(0); //创建行

优化后

Workbook wb = new SXSSFWorkbook(500);//创建工作溥
Sheet sheet = wb.createSheet(element.attributeValue("sheetName"));///创建Sheet页
Row headRow = sheet.createRow(0);//创建行

在这里插入图片描述
5.2 变量声明优化
优化前,设置Excel单元格内容的代码是这样的

@SuppressWarnings("unchecked")
	private static <T> void createContent4List(List<T> list, Sheet sheet, CellStyle cellStyle, Element element)
			throws Exception {
		String clazzString = element.attributeValue("class");
		// 反射创建对象实例
		Class<? extends Object> clazz = Class.forName(clazzString);

		List<Element> columns = element.elements();
		int lastRowNum = sheet.getLastRowNum();
		String dataType = null;
		// 根据list确认行数
		for (int i = 0; i < list.size(); i++) {
			Row contentRow= sheet.createRow(i + lastRowNum + 1);
			for (int j = 0; j < columns.size(); j++) {
				Method method = clazz.getMethod("get" + columns.get(j).attributeValue("name"));
				Object obj= method.invoke(list.get(i));
				String cellValue = initExcelValue(obj);
				Cell contentCell = contentRow.createCell(j);
				contentCell.setCellValue(cellValue);
				contentCell.setCellStyle(cellStyle);
				//数值过长,转换为double会变为科学计数法导出,所以不做处理
				dataType = columns.get(j).attributeValue("type");
				if (isDouble(contentCell.toString())&& !"String".equals(dataType)) {
					contentCell.setCellValue(Double.parseDouble(contentCell.toString()));
				}
			}
		}
	}

从代码中可以看出,在for循环中重复创建了大量的变量,在此方法没有执行完成之前,这些变量一直存在于内存中,这样就会占用大量不需要的内存空间,很有可能会出现耗尽耗尽的情况,因此优化点就是将变量的声明放在for循环外面,这样就不用重复创建变量,经过优化后的代码如下

扫描二维码关注公众号,回复: 5057528 查看本文章
@SuppressWarnings("unchecked")
	private static <T> void createContent4List(List<T> list, Sheet sheet, CellStyle cellStyle, Element element)
			throws Exception {
		String clazzString = element.attributeValue("class");
		// 反射创建对象实例
		Class<? extends Object> clazz = Class.forName(clazzString);
		List<Element> columns = element.elements();
		Row contentRow = null;
		Cell contentCell = null;
		Method method = null;
		String cellValue = null;
		Object obj = null;
		int lastRowNum = sheet.getLastRowNum();
		String dataType = null;
		// 根据list确认行数
		for (int i = 0; i < list.size(); i++) {
			contentRow = sheet.createRow(i + lastRowNum + 1);
			for (int j = 0; j < columns.size(); j++) {
				method = clazz.getMethod("get" + columns.get(j).attributeValue("name"));
				obj = method.invoke(list.get(i));
				cellValue = initExcelValue(obj);
				contentCell = contentRow.createCell(j);
				contentCell.setCellValue(cellValue);
				contentCell.setCellStyle(cellStyle);
				//数值过长,转换为double会变为科学计数法导出,所以不做处理
				dataType = columns.get(j).attributeValue("type");
				if (isDouble(contentCell.toString())&& !"String".equals(dataType)) {
					contentCell.setCellValue(Double.parseDouble(contentCell.toString()));
				}
			}
		}
	}

到此,综合以上几个优化点,Excel导出大数据CPU资源过高的问题就解决了,如果您有更好或不同的解决方案,欢迎留言交流,谢谢

猜你喜欢

转载自blog.csdn.net/kity9420/article/details/86624578