Redis client 連結池配置不當引起的頻繁 full gc
現象
筆者負責的一個RPC服務就是簡單的從Redis Cluster中讀取資料,然後返回給上游。理論上該服務的物件大部分都應該是朝生夕死的,但是筆者檢視gc log 的時候發現 age >=2 的物件還真有不少,甚至和age=1的物件差不多 。也就是說物件從eden晉升到 Survivor,之後的每次young gc 這些物件都是在 Survivor區域中移動,直到晉升到old 區域中。GC log 如下 :
解決過程
因為只需要檢視 Survivor中區域的物件,使用JVM自帶的命令就不太合適。 筆者推薦用 唯品會開發 vjmap(他只支援CMS不支援G1) ,他能檢視各個age的物件。筆者使用它檢視age>=2的堆疊,堆內物件分佈如下:
其中最令人奇怪的就是deps.redis.clients.jedis.Jedis這個物件。因為這是連結Redis Cluster的物件,理論上 只要流量沒有大的波動不會有大量的建立活動 。而且Jedis本身會持有 Sokect、OutputStream、byte[ ]等物件。
筆者找到了建立Jedis物件的地方進行埋點, 發現基本上每六分鐘就會銷燬和建立一批Jedis物件。因為知道Redis client 採用的是連結池的方式,就是看了一下GenericObjectPool程式碼,發現 有個定時任務檢測物件。關鍵程式碼如下:
從上面程式碼我們看出,每隔一段時間,就是檢測物件池裡面對象,要是發現物件空閒時間超過一定時間,就會強制回收;然後又發現連結少於minIdle了,開始建立物件,以滿足mindle。筆者所在公司封裝Redis client 設定的檢測輪詢時間為6分鐘。
上面問題已經找到了,解決就比較簡單了。因為配置的 mindle過大導致,導致連結池裡有大量空閒。專案中配置的mindle為32,修改為3測試 上線 觀察。之後gc log如下:
上圖中dx04是優化之後的,dx03是優化之前的,從圖中我們可以看出f ull gc次數由一週20次降為一週4次, young gc的時間平均下降了1.5ms左右(畢竟能減少物件在 Survivor中的移動 )
總結
作為專案的ower,我們一定要清楚瞭解業務特徵。看看gc log是否符合業務特徵應該呈現的gc log。如果不符合,使用合適的工具是查詢原因,你一定有所收穫。