記一次使用easyexcel匯入excel導致cpu跑滿的問題

翎野君發表於2024-06-03

記一次poi匯入excel引起cpu跑滿的問題
生產應用機器配置:8C 16G

週日突然收到告警,cpu持續15分鐘空閒時間小於10%,趕緊聯絡運維要日誌,透過分析dump_high_cpu

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND            
28830 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  16:12.49 java              
29157 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  11:43.56 java              
29991 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  14:31.26 java              
30187 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  14:06.83 java              
30408 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  13:02.85 java              
30599 jbossuse  20   0 12.9g 6.9g  27m R 100.9 44.6  12:44.31 java              
28840 jbossuse  20   0 12.9g 6.9g  27m R 99.1 44.6  15:44.46 java               
29165 jbossuse  20   0 12.9g 6.9g  27m R 95.5 44.6  15:37.87 java               
27522 jbossuse  20   0 12.9g 6.9g  27m S  0.0 44.6   0:00.00 java               
27523 jbossuse  20   0 12.9g 6.9g  27m S  0.0 44.6   0:00.68 java   

看出來,有8個執行緒跑到100%.透過pid去尋找對應java core 裡的執行緒,發現這個8個執行緒都是進行同一個操作

"default task-17" prio=10 tid=0x00007ff15802f000 nid=0x71ed runnable [0x00007ff1f10ac000]
   java.lang.Thread.State: RUNNABLE
	at org.apache.xmlbeans.impl.store.Locale.count(Locale.java:2049)
	at org.apache.xmlbeans.impl.store.Xobj.count_elements(Xobj.java:2050)
	at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTColsImpl.sizeOfColArray(Unknown Source)
	- locked <0x00000006e8d5c800> (a org.apache.xmlbeans.impl.store.Locale)
	at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.addCleanColIntoCols(ColumnHelper.java:115)
	at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.cleanColumns(ColumnHelper.java:56)
	at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.<init>(ColumnHelper.java:43)
	at org.apache.poi.xssf.usermodel.XSSFSheet.read(XSSFSheet.java:144)
	at org.apache.poi.xssf.usermodel.XSSFSheet.onDocumentRead(XSSFSheet.java:130)
	at org.apache.poi.xssf.usermodel.XSSFWorkbook.onDocumentRead(XSSFWorkbook.java:286)
	at org.apache.poi.POIXMLDocument.load(POIXMLDocument.java:159)
	at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:186)
	at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:73)

定位到佔滿cpu的操作,是用poi匯入execl ,找到程式碼處,發現已經限制了匯入的excel的大小為1MB,但是沒有限制匯入頻率,這樣的話,使用者短時間內可以頻繁匯入資料到系統.

那麼問題來了,頻繁匯入1MB的excel為什麼會導致cpu跑滿?拉取了gc日誌發現jvm在頻繁的ygc,平均幾秒就發生一次.並且在分析問題的這段時間,cpu仍然沒有下降,佔用cpu高的執行緒仍然在持續,會不會是這幾個執行緒在建立大量物件,導致ygc頻繁回收,而且回收的年輕代空間仍然不滿足執行緒的需要,進而引發cpu跑滿?
這是一個很合理的猜測,但是需要事實來證明,在確認這臺機器無法自行恢復之後,聯絡運維先拉取了dump檔案,然後重啟機器.

經過了漫長的等待,終於到手了dump檔案,分析dump檔案後發現,有大量的char[] 和list物件在生成.這個和猜測以及本身定位到的poi程式碼處實現一致.

最終問題定位後的描述如下:

在某個業務場景,報表匯入沒有頻次限制,導致使用者可以重複高頻次的匯入excel到系統,導致系統在用poi解析時,生成了大量的物件,並且poi在最終彙總物件時加了鎖,jvm年輕代在回收多次之後仍然不滿足執行緒所需,引發鎖自旋,導致cpu跑滿.

問題定位出來了,但是還有一點疑惑,為什麼1MB的物件在生產poi物件時,會佔用更多的記憶體呢?

原來,poi讀取excel有兩種方式,一種是使用者模式,另外一種是事件模式。使用者有封裝好的方法,使用簡單,但是會建立非常多的物件,耗記憶體,後者用來讀取excel,但不用把整個excel載入到記憶體,減少了至少10倍的記憶體使用

最終的疑惑也解決了,專案中使用的方式都是使用者模式,這才導致了大量記憶體的消耗,接下來該想想如何把專案中的讀取模式切換成事件模式了,以防其他地方再次出現今天這樣的問題。

相關文章