選單

一次線上突發頻繁fullGC的分析與解決

前情概要

一次線上突發頻繁fullGC的分析與解決

4月份某天下午剛上班,春困之際,整個人還不是非常的清醒,結果釘釘開始收到告警,線上一臺服務在非常頻繁fullGC,一下子,整個人清醒多了,這個不是一個簡單的告警,對服務的影響非常大。確實如此,沒過幾分鐘,下游服務開始呼叫超時告警

一次線上突發頻繁fullGC的分析與解決

一次線上突發頻繁fullGC的分析與解決

我們公司內部的APM工具是pinpoint,可以看到服務超時13:50~14:03這段時間內服務響應時間有很多超過了5000ms

一次線上突發頻繁fullGC的分析與解決

找到出問題的那臺例項

一次線上突發頻繁fullGC的分析與解決

紅線表示fullGC,基本上這個例項處於不可用的狀態,分發到這個例項的請求基本上也就是超時,其他例項此時正常,我們服務總共部署了五個例項,只有這個例項出了問題

快速恢復

下線出問題的例項,記得這裡先dump堆檔案

問題分析

原因分析

根據以上現象,猜測應該是某個不常用的請求或者某種特殊的場景導致記憶體載入了大量資料,正好這個請求是由出問題的這個例項來處理的。

因為服務了過了一會就恢復了正常,服務日誌裡也找不到任何的有用的資訊,分析陷入了瓶頸,但這個問題只要出現一次,就會導致服務基本上不可用,所以還是要找到根本的原因,徹底的根治這個問題,避免後續產生更大的影響。

我們的服務載入資料的途徑有限,要麼是資料庫查詢,要麼是外部介面返回,根據dump檔案其實可以看出來物件其實大部分都是我們內部的實體物件(這裡忘記截圖了),所以應該是資料庫查詢返回了大批次資料。

解決思路

JVM引數調整: 調整JVM引數,儘可能避免出現該問題

程式碼邏輯調整: 找到問題程式碼並修復

JVM引數調整

整個調整的思路是儘可能最小化“短暫物件”移動到老年代的數量,避免老年代快速膨脹,觸發majorGC或者fullGC,進而導致服務STW,影響業務,但是這個調整也無法避免程式碼導致的極端情況

調整新生代的大小:,, 我們堆大小為5g,調整新生代大小到2560M,為整個堆大小的一半,儘可能的讓更多的類可以放到新生代

調整物件晉升到老年代的年齡閾值: , CMS中該值預設為6,調整到15,讓物件儘可能保留在新生代,在新生代完成回收

調整survivor區與Eden區的比例: , 換算一下,Eden區大小等於2560M*0。8 = 2048M

程式碼邏輯調整

這裡的解決思路是,限制程式碼大批次資料查詢,找出程式碼裡大批次查詢資料庫的壞程式碼並修復

方案一:透過mybatis外掛,全域性查詢語句加上limit,限制最大的返回資料,但是我們的業務中,經常有關聯資料好幾萬條,這裡其實資料結構設計是不合理,這個limit大於好幾萬也就失去了意義,因為有些表單行記錄比較大,幾萬條記錄也有幾百兆,請求量大的時候,也會出現這個問題,而且也不能發現出問題的程式碼,專案程式碼太多了,看程式碼找問題只能看緣分,不靠譜

方案二:也是透過mybatis外掛,統計每次查詢結果的數量,大於某個閾值列印告警日誌,實時監控該日誌,根據日誌找到整個鏈路,進而找到出問題的程式碼

我這裡採用了第二種方案,外掛程式碼如下:

大部分程式碼都是mybatis的外掛模版程式碼,核心程式碼很簡單

程式碼邏輯: 某次查詢超過配置的條數時,列印warn日誌。並在日誌平臺配置對應日誌的釘釘告警

再次出現

一次線上突發頻繁fullGC的分析與解決

有了日誌,透過traceId馬上就能找到對應程式碼了,可以看到這裡從資料庫查詢30多萬資料到記憶體,觸發fullgc也是正常的

程式碼看起來沒啥問題呀,在看對應的查詢的mapper

這裡沒有做限制,當ids為null,全表查詢not_deleted的資料,30多萬條記錄全部返回

坑點和教訓

動態sql 如果所有條件都未匹配,不能直接查詢全表,應該返回為空,要在程式碼裡或者mapper sql中加以限制

最佳化業務資料結構,在程式碼里加上limit限制

資料庫層面也要做限制,如果這裡是大批次的刪除,可能業務影響會更大