记一次线上事故的罪魁祸首--poi的oom

最近发生了一次线上故障 事后复盘,发现引发当时的线上故障:内存急剧升高,docker重启
查看日志,发现在当时有一个商户在不断点击一个导出功能(该商户的此处数据达几十万),由于前端未做按钮点击禁用,导致用户的多次导出请求打到服务器中,进而内存急剧升高。但是并没有oom的报错,这是因为内存占用还没有到达oom的阀值,但已经达到了docker重启的阀值。

所以本次问题的主要原因有两点:

  1. 前端未做按钮禁用(当然,用户刷新页面后还是可以点击。。。)
  2. 导出功能未考虑到大数据量业务情形
  3. docker的重启阀值与jvm的堆内存分配不匹配(导致不断重启的诱因)

本次我们主要讨论第二点,利用poi进行excel导出时如何防止内存飙升?

首先我们看几个Workbook

  1. HSSFWorkbook(excel 2003)
    HSSFWorkbook 针对是 EXCEL2003 版本,扩展名为 .xls;所以 此种的局限就是 导出的行数 至多为 65535 行,此种 因为行数不够多 所以一般不会发生OOM。
  2. XSSFWorkbook (excel 2007)
    由于第一种HSSFWorkbook 的局限性而产生的,因为其所导出的行数比较少,所以 XSSFWookbook应运而生。其对应的是EXCEL2007+(1048576行,16384列)扩展名 .xlsx,最多可以 导出 104 万行,不过 这样 就伴随着一个问题—OOM 内存溢出,原因是 你所 创建的 book sheet row cell 等 此时是存在 内存的 并没有 持久化,那么 随着 数据量增大 内存的需求量也就增大,那么很大可能就是要 OOM了。
  3. 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());
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值