當前位置:
首頁 > 最新 > Redis連接池的相關問題分析與總結

Redis連接池的相關問題分析與總結

問題表象:服務端連接未釋放

問題背景:商品系統在運行過程中發生過一次Redis服務端連接數超限的問題。截圖未保存,表現是:商品服務停掉,但RedisServer端看到的TCP連接任然存在,而且是 ESTABLISHED狀態,導致的直接結果就是每次商品重啟都會創建400個(minIdle=400)新的redis連接,而且停止的時候還不釋放,重啟幾次之後RedisServer的連接就超過上限10000,無法再創建新的連接。

一、初步分析:

TCP是可靠協議,斷開是需要經過4次揮手,既然服務端的連接未釋放,那十之八九是客戶端沒有發送斷開請求;

即使客戶端沒有主動斷開連接,但是這個連接實際上已經失效了,RedisServer為啥沒有自動檢測,而一直沒有釋放?

二、定位與排查過程:


首先懷疑的點是商品在停止的時候沒有釋放Jedis的連接,所以趕緊把JedisCluster的代碼看了,果然有個close方法:

直接把這個方法註冊到spring的destroy-method中:

在性能測試環境上重新做下實現,發現此方法一直沒有被調用,不解,看了下spring源碼,調用destroy-method的觸機有兩個:

顯示的調用org.springframework.context.support.AbstractApplicationContext.close()方法;

顯示的調用org.springframework.context.support.AbstractApplicationContext.registerShutdownHook(),這樣在程序正常停止的時候(kill,非kill -9 )就可以觸發shutdownHook的調用:

springContainer的stop方法就是調用spring-context的close方法,最終會遍歷每個bean的destroy-method。

Redis客戶端斷開連接,服務端連接未釋放的根本原因分析:

這裡選用了常用的抓包工具,tcpdump + wireshark(wireshark默認不支持redis協議。

然後啟動商品服務和停止商品服務,抓包觀察。

我在做實現的時候搞個兩個JedisCuster實例,每個實例的初始連接數(minIdle)都是1,有個實例註冊了destroy-method,另一個實例未註冊。

1.第一個網路包是商品服務的啟動過程,6個TCP包,報內容就是簡單的TCP3次握手,創建了2個TCP鏈接

2.第二個網路包是商品服務的停止過程,這裡有看出區別了:第一個JedisCluster實例在結束的時候調用close命令,向RedisServe端發送了Quit指令,RedisServer返回OK。然後RedisServer主動發送FIN包,請求關閉連接,客戶端ACK響應,然後客戶端直接發送RST中斷連接,這確保了服務端RedisServer可以斷開連接。另一個JedisClsuter就直接給RedisServer發送一個RST,然後就沒了。

這裡面有兩個疑問?

(1). 為什麼客戶端沒有遵循TCP的4次回收規範直接發送RST包中斷連接?

(2).我們線上這麼長時間都沒有配置JedisCluster的close函數。

先解答(2)的問題,我通過幾次實現,發現即使我們什麼都不做,直接停止服務,甚至kill -9 JVM線程,Client也會向RedisServer發送RST請求(我猜是OS自己做的,但具體沒有再深入,有時間再看),但是並不是每次都發,會偶發性的丟包,一旦丟包,RedisServer就SB了,Client端私自把連接關閉了,然Server端卻不知道,所以連接一直保持。

kill -9的網路包:

再看(1)的問題,為啥調用close的情況下Client還是給Server端返回RST包呢。查看了JedisCluser的源碼,找到了原因:

Jedis在創建Socket連接的時候配置了一個參數:socket.setSoLinger(true, 0); 這個參數的作用就是:在此socket調用close方法的時候不發送FIN包,而是直接發送RST包。

為什麼Jedis要特意這麼做呢?

簡單來說就是TCP是一個可靠消息,而且TCP的4次揮手過程中存在一個半關閉狀態,過程比較糾結:

『假設Client端發起中斷連接請求,也就是發送FIN報文。Server端接到FIN報文後,意思是說「我Client端沒有數據要發給你了「,但是如果你還有數據沒有發送完成,則不必急著關閉Socket,可以繼續發送數據。

所以你先發送ACK,「告訴Client端,你的請求我收到了,但是我還沒準備好,請繼續你等我的消息「。這個時候Client端就進入FIN_WAIT狀態,繼續等待Server端的FIN報文。當Server端確定數據已發送完成,則向Client端發送FIN報文,「告訴Client端,好了,我這邊數據發完了,準備好關閉連接了「。

Client端收到FIN報文後,「就知道可以關閉連接了,但是他還是不相信網路,怕Server端不知道要關閉,所以發送ACK後進入TIME_WAIT狀態,如果Server端沒有收到ACK則可以重傳。「,Server端收到ACK後,「就知道可以斷開連接了「。

Client端等待了2MSL後依然沒有收到回復,則證明Server端已正常關閉,那好,我Client端也可以關閉連接了。Ok,TCP連接就這樣關閉了!』

TCP建立連接與釋放鏈接的流程:

總結:

關於RST的這個問題大家可以參考一下兩個文章:

http://xiaoz5919.iteye.com/blog/1685138(看評論)

http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection](http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection)(解釋的很好,建議深讀)


接下來再看第二個問題『即使客戶端沒有主動斷開連接,但是這個連接實際上已經失效了,RedisServer為啥沒有自動檢測,而一直沒有釋放?』,而且第二個問題還引發了上周我們線上的另一個問題。

在商品系統出現連接未釋放的問題之後,我緊急求救了啊泉,啊泉憑靠自身多年的運維經驗很快反應這是RedisServe的一個配置的問題:

這個配置的意思是:RedisServer會自動關閉超過N seconds時間的idel連接,如果配置0那就是用不關閉。(我們現在所有的集群都是默認0)。啊泉當機立斷,改成600。過了一會,Server端連接全部釋放,應用恢復正常。

本來故事當這裡就應圓滿結束了,然後面幾周卻不消停,最初從財務系統開始,到訂單系統、積分系統都開始偶發性報Jedis錯誤:

還有商品系統一天到晚都偶發性報錯:(部分原因可能是mget的原因)

我們Check了下財務、訂單、積分公用的Redis集群,除了內存使用量增長之外(單機超過1.3G),其他並無任何異常,初步猜想『這個Redis集群撐不住了』?拆,第二天就把購物車的Redis-DB拆出來了,但是錯誤依舊。

難道還是內存的問題,當晚又把新的Redis-DB的key清理了下,把不用的key全刪了,把使用內存降到350M左右。

公共Redis-DB的內存:

購物車的Redis-DB的內存:

然降了內存之後錯誤依舊,SB了。

中間翔把jedis的連接超時和請求超時時間都配置成20s,ms也解決了問題。但是我們討論下來,這不是一個好的解決方案,這是飲鴆止渴的法子。我們需要從源頭找問題,通過翻查資料和源碼,初步判斷是Client和Server的連接出了問題,感覺像是Jedis用了一個壞的連接去請求數據,然後Server端異常返回。Jedis官方有個貼了很詳細的討論了這個問題:

https://github.com/xetorthio/jedis/issues/965。這篇文章很長,結論就是這個問題一般是RedisServer的連接壞了引發了的。

壞了有幾種可能:1. 網路抖動,連接失效。2. RedisServer這端主動關閉了,然Client端不知道(網路丟包)。

所以解決方案有兩個:

RedisServer端配置timeout = 0,用不釋放連接;

Client需要定時檢測和清理死鏈;

我們目前採用的解決方式是1,2都用了(2是最優解,因為網路環境相對複雜,網路斷開的情況時有發生,所以客戶端需要有檢測死鏈的機制)。1的相關配置如下,主要有兩個重要參數:

配置這兩個參數之後問題解決,且商品也不報錯了,至此這兩個問題算是解決了。

問題總結:

客戶端與服務端之間的連接問題,可能存在客戶端斷開了連接服務端不知曉、服務端斷開了連接客戶端不知曉,為了解決這兩個問題,需要做的就是服務端和客戶端定期檢查,客戶端通過setTestWhileIdle(Boolean.True)、setTimeBetweenEvictionRunsMillis(xxx) 來定期檢查方式死鏈;服務端通過設置超時時間來做到檢查連接的問題。

後續:昨晚看VIP的運維分享,他們不建議RedisServer端的timeout配置成0,同樣一個原因,網路異常的情況不可避免(或者應用端Kill -9),所以Server端同樣需要有檢查和清理死鏈的能力。

-END-

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Java進階與雲計算開發 的精彩文章:

TAG:Java進階與雲計算開發 |