生産大文件下載導致OOM,順藤摸瓜拿下

數據智能相依偎 2024-04-03 21:58:38

上周遇到了生産環境 OOM 的問題,找了一番之後基本定位了是大文件下載導致的問題,于是在網上搜羅了一番文章,下面分享一篇優質的解決方案,整個排查思路非常清晰,小白可以直接對照著來排查。

事故發生

上周五下午運營人員反饋,筆者所負責的後台系統從 14 點以後就卡卡的,雖然頁面能夠正常加載,但是一直處于數據加載中,數據也提交不了,懷疑筆者的系統有BUG,當聽到運營人員的反饋我的第一反應是這不可能啊,這麽簡單的一個後台系統,還能出事故?

處理流程

摘除其中一台服務器用于保留現場,其他服務器先重啓,保證系統可用。下載GC日志,系統dump文件用于分析

GC log分析

系統啓動參數,JVM內存分配:-Xmx4096m -Xms4096m -Xmn2560m

觀察日志可知系統每隔 40S 發生一次 Full GC,耗時 200 毫秒,回收以後系統老年代占用也不大,才 15M,但是新生代回收完還有 2 個 G。

有點不可思議,竟然不是老年代塞滿了數據,而是新生代塞滿了數據。

初步推測是新生代數據要晉升到老年代,結果放不進去而引起的 Full GC。

使用 MAT 對 Dump 文件進行分析

通過總圖可以看出來目前系統內存占用超過 2 個 G:

點擊 Histogram 進行進一步分析,看出系統中占用最多的是byte[]

點擊List Objects進入income引用統計界面

層層點開,發現byte[]被 ResponseEntity 對象所引用,且數量不小

翻閱代碼

1)在系統中找到唯一ResponseEntity有關的代碼

2)這代碼看似沒什麽問題啊,這不是很正常的文件下載麽???我去看看用戶下載了啥,跑到目錄文件查看一下下。

我的天,用戶下載的是一份2.4G的大文件,代碼中FileUtils.readFileToByteArray(file) 的方式是把整個文件讀取到內存再輸出流裏寫入,此時內存不夠分配,又塞不進老年代,只能是 Full GC 了。

3)成功破案了,用戶下載了一份大文件,文件先加載到內存才往外寫,抹淚。。。。

解決方案

使用FileSystemResource

@GetMapping("/down")public ResponseEntity download(@RequestParam("uri") String uri) throws IOException {  File file = new File(uri);  if (!file.isFile()) {   throw new ServiceException("文件不存在");  }  String filename = FilenameUtils.getName(uri);  HttpHeaders headers = new HttpHeaders();  headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));  HttpStatus status = HttpStatus.OK;  return new ResponseEntity<>(new FileSystemResource(file), headers, status);}

使用緩存流,邊讀邊寫

@GetMapping("/down")public void download(@RequestParam("uri") String uri, HttpServletResponse response) throws IOException { File file = new File(uri);  if (!file.isFile()) {   throw new ServiceException("文件不存在");  }  String filename = FilenameUtils.getName(uri);  response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));  try (FileInputStream fileInputStream = new FileInputStream(file);    BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(response.getOutputStream())) {    FileCopyUtils.copy(bufferedInputStream, bufferedOutputStream);  } finally {   // 使用的是try-with-resources  }}

文件存儲到 oss 或者是七牛雲

將文件存儲到 oss 或者是七牛雲,繞過服務器下載

來源:飛天小牛肉

0 阅读:0

數據智能相依偎

簡介:感謝大家的關注