本文是線上問題處理案例系列之一,旨在通過真實案例向讀者介紹發現問題、定位問題、解決問題的方法。本文講述了從垃圾回收耗時過長的表象,逐步定位到資料庫連線池保活問題的全過程,並對其中用到的一些知識點進行了總結。
大促期間,某介面超時次數增多,經排查直接原因是GC耗時過長,檢視監控FullGC達500ms以上,介面超時時間與FullGC發生時間吻合。
圖1 FullGC耗時監控
1、 GC耗時過長,說明記憶體中垃圾物件很多。
2、 首先懷疑是否有記憶體漏失,觀察FullGC後堆記憶體回收情況,尚屬正常,暫時排除記憶體漏失原因。
圖2 發生FullGC後堆記憶體回收監控
3、 推斷FullGC耗時過長是否因為老年代有大量死亡物件,遂匯出FullGC前後堆記憶體dump,通過比對「保留大小」,發現FullGC後大量資料庫相關物件被回收。
圖3 堆記憶體物件分析
4、 資料庫連線正常應該不會頻繁建立和斷開,進入老年代後,正常不應該被回收,通過堆dump內容OQL分析每個資料庫連線數量,發現很多庫連線數都大於「maxActive」數量,可以肯定有很多失效連線。
5、 初步判斷直接原因是很多失效資料庫連線進入老年代,導致FullGC耗時過長。
6、 懷疑連線池驗證週期過長,導致資料庫因空閒過長關閉連線,將連線池引數「
timeBetweenEvictionRunsMillis」由1分鐘調整到10秒,問題依舊。
7、 閱讀DBCP原始碼,發現是通過
org.apache.commons.pool.impl.GenericObjectPool.Evictor定時任務,按照timeBetweenEvictionRunsMillis設定的週期定時驅逐失效連線,驅逐條件:若連線空閒時間大於「minEvictableIdleTimeMillis」,則會驅逐連線,等待垃圾回收。若開啟「testWhileIdle」則會執行「validationQuery」。進一步閱讀程式碼,發現執行「validationQuery」後,連線空閒時間並不會重新計算,導致連線在業務低谷時很容易被淘汰,而資料庫連線會關聯大量物件,建立、回收成本昂貴,並且影響GC。
8、 反向思考,為何只有在大促期間才發生問題?
圖4 平時和大促時回收頻率對比
可以看到平時由於業務量小,GC不頻繁,過期連線沒有達到進入老年代閾值,在年輕代被回收。而大促時業務量大,GC頻繁,連線在進入老年代以後才過期,導致老年代FullGC時間過長。
9、 至此,基本可以肯定問題原因是資料庫連線池不具備「保活」能力,導致連線不斷淘汰和新建,在業務高峰時段,連線進入老年代然後失效,造成FullGC耗時過長,最終導致介面超時次數增多。
方案1:改為G1回收器,對老年代回收是分塊進行,可以防止長時間停頓。另外預設MaxTenuringThreshold值是15,可以防止失效連線過早進入老年代;
方案2:
minEvictableIdleTimeMillis設定為0,使資料庫連線不會自動失效,進入老年代以後一直存活,避免在老年代失效回收;
資料庫連線池並不具備通常理解的「保活」能力,資料庫連線在業務不活躍的應用中,會不斷淘汰和重連,而連線會通過虛參照方式(
com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference)攜帶大量物件,如果連線存活時間內YGC次數達到壽命閾值,則會進入老年代,老年代是使用「標記-清除」演演算法,回收成本更高,進而造成FullGC耗時過長。
1、 Druid連線池同樣存在不能「保活」問題,較新版本提供「KeepAlive」選項(未驗證);
2、 Druid連線池設定的「validationQuery」語句通常並不會被執行,MySqlValidConnectionChecker在檢查連線有效性時,會判斷驅動是否實現pingInternal方法,如果實現則會通過此方法驗證有效性。MySQL的JDBC驅動實現了該方法,因此「validationQuery」設定的語句通常不會執行;
圖5 連線有效性校驗程式碼
3、 DBCP和Druid連線池預設都是FILO,如果業務不繁忙,會導致只有最前邊的連線被使用-歸還-使用,後邊連線基本都在無謂的驅逐、重建連線;
4、 虛參照對GC的影響:這些參照只有經過兩次GC才能被回收掉,如果進入老年代,則必須經過兩次FullGC才能釋放記憶體。本例中由於不斷有新的虛參照物件在老年代失效,導致FullGC後,記憶體水位仍然偏高,會加劇GC壓力。新版本JVM已對此做了優化,一次GC可以回收掉;
5、 類似的影響還有finalize方法;
6、 CMS回收器預設MaxTenuringThreshold為6,而ParallelGC和G1均預設15;
本文對資料庫連線失效引起的GC問題進行了詳細分析,希望讀者通過本文對資料庫連線「保活」機制、GC問題基本分析方法有所收益,後續該系列文章會繼續推出其他案例分享。
作者:京東零售 王利輝
內容來源:京東雲開發者社群