导入excel数据内存暴涨问题

670 阅读1分钟

背景

  • excel批量导入数据功能导入了5w数据,出现内存剧增的现象,最高达288M,服务OOM,最后降到149M

排查过程

第一次

  • 读excel,也是5w的数据,内存70+

  • 根据日志可以定位到rows, err := fillExcelFile.GetRows(sheetName)这行代码导致内存突然增加,GetRows是第三方包的一个函数

    func GetExcelRows(infra *middleware.Infra, ossFileData []byte) ([][]string, error) {
        ...
    	fillExcelFile, err := excelize.OpenReader(reader)
    	...
    	rows, err := fillExcelFile.GetRows(sheetName)
    	...
    	return rows, nil
    }
    
  • 用golang pprof

    • 在单元测试中加入代码

      import _ "net/http/pprof"
      
      go func() {
          http.ListenAndServe("0.0.0.0:8080", nil)
      }()
      
    • 在浏览器中输入http://localhost:8080/debug/pprof/可以看到一些heap、goroutine信息

    • go tool pprof -inuse_space http://127.0.0.1:8080/debug/pprof/heap -inuse_space:查看进程正常使用的内存信息;与之对应的还有-alloc_space:查看总分配的内存信息

    • top10;prepareSheetXML方法直接耗了70M

  • 5w的数据,耗了70M,还能接受,这次就没有纠结太多。

第二次

  • 校验数据,5w不合法的数据加上样式,写入excel并返回。到了写这一步,内存直接飙到了288M。

  • 根据日志,基本可以定位到了问题点,又是excel这个第三方包,在循环里SetSheetRow。

    func (s *student) setInvalidFieldWarningStyle(templateData []byte, values [][]interface{}) *excelize.File {
    	f, err := excelize.OpenReader(bytes.NewReader(templateData))
    	...
    	for i, value := range values {
           	 ...
    		err := f.SetSheetRow(sheetName, startRow, &setValue)
    		...
    	}
    	return f
    }
    
  • 上GitHub看了issue。果然,好多反映内存问题。

  • 还是用golang pprof分析了一下

  • 看了一下该第三方的包的文档,换成了流的方式写

    func (s *student) setInvalidFieldWarningStyle() error {
    	streamWriter, err := file.NewStreamWriter(importservice.TemplateExcelSheetName)
    	...
    	for i, param := range s.inValidParams {
    		...
    		for _, column := range param.InvalidColumn {
    		...
    		startRow := fmt.Sprintf("A%d", i+importservice.TemplateExcelStartRow)
    		err := streamWriter.SetRow(startRow, tempRows)
    		...
    	}
    
    	return nil
    }
    
  • 果然,内存降下来了

总结

  • 用golang pprof工具去分析内存,定位问题,才能进行下一步的优化

参考