1. 程式人生 > 其它 >5.堆記憶體溢位以及匯出Excel表格超時問題解決(效能優化心得)

5.堆記憶體溢位以及匯出Excel表格超時問題解決(效能優化心得)

業務場景是主要是查詢或者匯出某家分銷商一個月內產生的退票退款訂單資料。由於涉及到機密資料,因此不便展示效果圖。只記錄一下遇到的兩個經典的問題以及解決思路以供參考。   出現的問題: 因為測試環境測試不出生產環境的問題,生產環境訂單量較大,我看了一下訂單加在一起有三千多萬條,因此部署到生產環境之後匯出功能出現了兩個問題,一個是記憶體溢位,另一個是匯出超時。   思路: 因為匯出的資料格式和查詢的資料格式相同,因此開發時,匯出的邏輯是線上程池中分頁查詢每一頁資料然後組裝完成Excel,因此我優化匯出功能時其實是包含著優化查詢功能,查詢的邏輯是執行緒池按時間分段查詢資料。   解決步驟如下: 第一階段:上生產之後報記憶體溢位(OutOfMemory:Heap Space)我首先檢查了一下程式碼,主要考慮堆溢位的原因,發現了一些沒有必要的或者肉眼可見的問題進行了第一次優化,具體內容如下: 一、   資料庫方面:從查詢sql欄位由“*”減少到只查詢需要展示的幾個欄位,提高查詢速度,降低記憶體容量。 二、   Excel生成工具:因為Excel生成工具我用的是HSSFWorkbook,這個生成的資料流是往記憶體中寫的,當資料量過大時,肯定會超出記憶體承受能力。從HSSFWorkbook改成SXSSFWorkbook,SXSSFWorkbook是專門用於大資料量時使用,SXSSFWorkbook是streaming版本的XSSFWorkbook,它只會儲存最新的rows在記憶體裡供檢視,在此之前的rows都會被寫入到硬盤裡,因此可以降低記憶體的消耗。 三、   減少建立物件的數量:在每頁生成Excel行數時,每個SXSSFCell我都建立了一個cell物件來設定換行樣式和資料資訊,因為是堆溢位,說明物件建立的太多了。而且我生成Excel資料流時使用的是執行緒池,需要記憶體同時存放多個執行緒的cell物件,這樣太費記憶體了,因此我去掉了用物件接收cell,而是直接createCell,然後設定有樣式使用getCell。降低了記憶體的壓力。 四、   使用Redis快取:針對使用者相同的查詢條件,將查詢條件和查詢結果快取到Redis中,這樣,下次使用者進行分頁查詢時就不需要再去訪問資料庫重新組裝資料了,降低資料庫訪問壓力。 經過上面第一階段的優化,我又重新上了生產,再次測試一萬以上的資料匯出,好吧,超時(一分鐘為最大訪問時間)。   於是,我進行了第二階段的優化: 一、   邏輯上:之前因為匯出的資料格式和查詢的資料格式相同,因此開發時,匯出的邏輯是線上程池中分頁查詢每一頁資料然後組裝完成Excel。但是直接呼叫查詢方法會重複呼叫相同邏輯,比如查詢時,每次都會檢查Redis中是否已存在相同的查詢條件,這個檢查也是比較耗時的。於是我改成了不直接呼叫查詢方法,在組裝Excel資料前,先檢查Redis中資料是否已存在,若存在,則直接使用資料去迴圈組裝每頁資料,這樣節省了查詢Redis的時間。 二、   查詢方式修改:由於主表資料量太大,表的總資料量有3千多萬,欄位數50個,現在已經分表了,資料量太大,查詢起來確實比較耗時。建議我改一下查詢邏輯,不要從主表下手查詢,改成其他查詢方式。因為我這個功能主要是查詢某種符合條件的資料,正常查詢邏輯的話,是應該從主表查詢出資料,然後再進行篩選。但是查詢主表的話,需要將多種狀態的資料查詢出來然後再根據邏輯篩選,主表資料量大的一個月的資料量有十幾萬訂單,我改成了直接查詢特定表的狀態符合當前查詢條件的資料,這樣就直接可以根據特定表與主表的關聯查詢出符合條件的主表資料,降低了主表的查詢壓力,節省了查詢時間。 三、   匯出檔案方式:之前是分頁生成資料流,然後將生成的二進位制流傳給前端。這樣的弊端是,在大資料量比如一個月有一萬三四的資料時,那麼後端將這麼大的二進位制流傳給前端是需要時間傳送的,前端再將二進位制流生成檔案給使用者,整個過程也是比較耗時的。因此我改成了將資料流直接上傳壓縮成zip包上傳到雲上,然後給前端一個檔案的連結,前端再從雲上下載壓縮包給使用者。這個過程省去了後端將二進位制流傳給前端的時間。 經過第二階段的優化,再次上生產,嘗試匯出最大資料量的資料,只需要17秒即可獲取壓縮檔案。解決了超時的問題。   這個優化前後大概花了兩三天時間,晚上睡覺都夢見如何解決這個問題,我都在想這個問題如果解決不了我咋整了,想的有點兒遠了···這個問題我反思了一下,一是對程式碼開發環境大資料量沒有一個深刻的認識,二是沒有之前沒有碰到過這種效能問題,經驗不足。通過這個問題的教訓,我覺得還是有必要多來幾個這樣的功能鍛鍊一下,除了JVM之外,效能優化還是有很多方面是存在優化點的,在優化過程中可以列印一下耗時或者用一些監控工具看一下記憶體等等,來定位具體的優化點。