最近发生了一次线上故障 事后复盘,发现引发当时的线上故障:内存急剧升高,docker重启
查看日志,发现在当时有一个商户在不断点击一个导出功能(该商户的此处数据达几十万),由于前端未做按钮点击禁用,导致用户的多次导出请求打到服务器中,进而内存急剧升高。但是并没有oom的报错,这是因为内存占用还没有到达oom的阀值,但已经达到了docker重启的阀值。
所以本次问题的主要原因有两点:
- 前端未做按钮禁用(当然,用户刷新页面后还是可以点击。。。)
- 导出功能未考虑到大数据量业务情形
- docker的重启阀值与jvm的堆内存分配不匹配(导致不断重启的诱因)
本次我们主要讨论第二点,利用poi进行excel导出时如何防止内存飙升?
首先我们看几个Workbook
- HSSFWorkbook(excel 2003)
HSSFWorkbook 针对是 EXCEL2003 版本,扩展名为 .xls;所以 此种的局限就是 导出的行数 至多为 65535 行,此种 因为行数不够多 所以一般不会发生OOM。 - XSSFWorkbook (excel 2007)
由于第一种HSSFWorkbook 的局限性而产生的,因为其所导出的行数比较少,所以 XSSFWookbook应运而生。其对应的是EXCEL2007+(1048576行,16384列)扩展名 .xlsx,最多可以 导出 104 万行,不过 这样 就伴随着一个问题—OOM 内存溢出,原因是 你所 创建的 book sheet row cell 等 此时是存在 内存的 并没有 持久化,那么 随着 数据量增大 内存的需求量也就增大,那么很大可能就是要 OOM了。 - SXSSFWorkbook(excel 2007后,poi使用3.8+版本)
专门处理大数据,对于大型excel的创建且不会内存溢出的,就只有SXSSFWorkbook了。
它的原理很简单,用硬盘空间换内存(就像hashmap用空间换时间一样)。 SXSSFWorkbook是streaming 版本的XSSFWorkbook,它只会保存最新的excel rows在内存里供查看,在此之前的excel rows都会被写入硬盘里(Windows电脑的话,是写入到C盘根目录下的temp文件夹)。被写入到硬盘里的rows是不可见的/不可访问的。只有还保存在内存里的才可以被访问到。
很不幸,本次我们故障服务,采用的导出方式就是XSSFWorkbook,它可以导出大量的数据,与此同时会迅速拉升内存,进而导致oom
对于XSSFWorkbook 、SXSSFWorkbook,我做了两组测试:导出100万条数据到excel中,分析结果如下(分析工具是yourkit):
1.采用XSSFWorkbook ,没能导出成功,报oom异常,内存使用情况如下:
2.采用SXSSFWorkbook,导出成功,内存使用情况如下:
综上,在上万条数据导出时,要考虑使用SXSSFWorkbook,不然很容易造成线上故障
测试代码如下:
/**
* @ClassName: BigDataEntity
* @Description: 大数据量导出
* @Author: Yu RuiXin
* @Date: 2020/1/17 21:09
*/
public class BigDataEntity {
@Excel(name = "c1")
private String c1;
@Excel(name = "c2")
private String c2;
@Excel(name = "c3")
private String c3;
@Excel(name = "c4")
private String c4;
@Excel(name = "c5")
private String c5;
@Excel(name = "c6")
private String c6;
public BigDataEntity(String c1, String c2, String c3, String c4, String c5, String c6) {
this.c1 = c1;
this.c2 = c2;
this.c3 = c3;
this.c4 = c4;
this.c5 = c5;
this.c6 = c6;
}
public static BigDataEntity build(int i) {
return new BigDataEntity((i + "一一一一一一一一一一一一一一"),
(i + "二二二二二二二二二二二二二二"),(i + "三三三三三三三三三三三三三三"),
(i + "四四四四四四四四四四四四四四"),(i + "五五五五五五五五五五五五五五"),(i + "六六六六六六六六六六六六六六"));
}
public String getC1() {
return c1;
}
public void setC1(String c1) {
this.c1 = c1;
}
public String getC2() {
return c2;
}
public void setC2(String c2) {
this.c2 = c2;
}
public String getC3() {
return c3;
}
public void setC3(String c3) {
this.c3 = c3;
}
public String getC4() {
return c4;
}
public void setC4(String c4) {
this.c4 = c4;
}
public String getC5() {
return c5;
}
public void setC5(String c5) {
this.c5 = c5;
}
public String getC6() {
return c6;
}
public void setC6(String c6) {
this.c6 = c6;
}
}
@Test
public void test3() throws IOException {
StopWatch sw = new StopWatch("test");
sw.start();
List<BigDataEntity> list = new ArrayList();
for (int i = 0; i < 1000000; i++) {
System.out.println(i);
list.add(BigDataEntity.build(i));
}
XSSFWorkbook wb = new XSSFWorkbook();
XSSFRow row;
XSSFCell cell;
XSSFSheet sheet = wb.createSheet();
for (int i = 0; i < list.size(); i++) {
System.out.println(i+"---------------------------");
//创建表格行
row = sheet.createRow(i);
//根据表格行创建单元格
int j = 0;
BigDataEntity bd = list.get(i);
cell = row.createCell(j++);
cell.setCellValue(bd.getC1());
cell = row.createCell(j++);
cell.setCellValue(bd.getC2());
cell = row.createCell(j++);
cell.setCellValue(bd.getC3());
cell = row.createCell(j++);
cell.setCellValue(bd.getC4());
cell = row.createCell(j++);
cell.setCellValue(bd.getC5());
cell = row.createCell(j++);
cell.setCellValue(bd.getC6());
}
wb.write(new FileOutputStream("D:/excel/test3-"+System.currentTimeMillis()+".xlsx"));
sw.stop();
System.out.println(sw.prettyPrint());
}
@Test
public void test4() throws Exception {
StopWatch sw = new StopWatch("test");
sw.start();
List<BigDataEntity> list = new ArrayList();
for (int i = 0; i < 1000000; i++) {
System.out.println(i);
list.add(BigDataEntity.build(i));
}
SXSSFWorkbook wb = new SXSSFWorkbook();
SXSSFRow row;
SXSSFCell cell;
SXSSFSheet sheet = wb.createSheet();
for (int i = 0; i < list.size(); i++) {
System.out.println(i+"---------------------------");
//创建表格行
row = sheet.createRow(i);
//根据表格行创建单元格
int j = 0;
BigDataEntity bd = list.get(i);
cell = row.createCell(j++);
cell.setCellValue(bd.getC1());
cell = row.createCell(j++);
cell.setCellValue(bd.getC2());
cell = row.createCell(j++);
cell.setCellValue(bd.getC3());
cell = row.createCell(j++);
cell.setCellValue(bd.getC4());
cell = row.createCell(j++);
cell.setCellValue(bd.getC5());
cell = row.createCell(j++);
cell.setCellValue(bd.getC6());
}
wb.write(new FileOutputStream("D:/excel/test4-"+System.currentTimeMillis()+".xlsx"));
sw.stop();
System.out.println(sw.prettyPrint());
}