1. 程式人生 > >使用Spring的Bean生命週期特性釋放Redis連線池案例

使用Spring的Bean生命週期特性釋放Redis連線池案例

簡介

寫這篇文章的初衷源於前幾天公司的Redis連線池溢位事件,後來是藉助SpringBean的生命週期特徵解決了連線池溢位問題。基礎稍微好點的人應該知道,spring管理的bean,可以自定義初始化方法init,和bean銷燬方法destroy,但是以前只是知道有這些特性,工作中從來沒有用過,因此比較陌生。

事件還原

連續兩天,公司redis連線池溢位,好多專案各種報錯,群上開始討論redis的問題,有的說擴大連線池連線數量,有的說定時重啟redis,在我看來這些都解決不了根本問題,然後我說了下自己的看法,結果公司大佬讓我去研究問題出在哪裡,原以為問題很嚴重,結果開啟程式碼一看,原來是工具類的一個低階錯誤。

Redis原有的工具類如下

Redis配置檔案的一部分

分析

        很明顯,RedisClusterDao工具類並沒有加入到spring容器中做統一管理(在配置檔案中也沒有將Redis工具類加入到Spring容器),其他類在使用的時候都是以new的方式建立新的物件,因此工具類中定義的init方法並不會在new的時候執行,destroy方法也不會在工具類例項銷燬時執行,也就是說shardedJedisPool連線池並沒有得到釋放。

        再看配置檔案,MaxTotal的值為-1,一般情況下,-1表示不限制,放在這裡應該就是表示不限制redis連線的數量,具體含義我並沒有細查,也沒查到。

        同時,公司的這臺Redis服務是開發人員本地和測試環境伺服器共用的,也就是說,開發人員啟動本地服務,然後服務中建立了多個redis連線,接著再把這個服務關掉,此時這幾個連線並沒有釋放,注意配置檔案中的timeout是6000秒,即100分鐘,假設這個程式設計師在100分鐘之內重啟了10次本地服務,一次啟動建立10個連線,那麼就有100個連線不被釋放,注意這100個連線並不是到了100分鐘立即全部釋放,他是有時間差,假設公司有100個開發人員,那麼會產生10000個連線,由於配置檔案中並沒有限制連線數,極端情況下可能更多,所以連線池溢位是遲早的事。

我的解決方案

    A) .控制redis連線池最大連線數量。

    B) .將redis工具類加入到Spring容器做統一管理。

    C) .為了確保destroy方法執行,可通過Spring事件+監聽器再次釋放redis連線池。

可行性分析

    A方案被大佬質疑,說是擔心控制連線數後,應用的連線數會不夠

    B方案也被大佬否定,因為說之前就是這樣做的,說是出什麼問題,所以才用現在的方式。我猜想是因為服務執行過程中因為記憶體問題,工具類的Bean被回收(Spring中即使是Single的單例Bean也會在記憶體極度匱乏情況下被回收),因此報錯。

    C方案,由於公司架構使用的不是標準的Spring體系(SpringIOC,SpringAOP+CXFService),因此除過Refresh事件,其他事件並不會產生,比如這裡需要用到的stop事件,因此這個方案也是不可行。

最終解決思路

    後來,基於原有工具類的靈感,我藉助Spring的Bean生命週期特性,新增destroy方法,並且用註解@PreDestroy標識。但是原有的工具類在很多地方有有用到,而且我不知道B方案中大佬所說的問題是什麼問題,因此原有的工具類我沒有做任何修改。

    雖然原有工具類不能動,但是可以注意到,原有工具類中的shardedJedisPool連線池變數是static型別,因此,我重新定義了一個類,如下

將新定義的類加入到Spring容器當中,這樣在容器關閉的時候會呼叫destroy方法,redis連線池也就得到了釋放,但是需要注意,為了方式B方案中我的猜想發生(Bean被回收),我在當前類實現了介面ApplicationLister,這是Spring一個非常中要的介面,而且跟事件相關,因此我猜想實現這個介面的Bean全程不會被回收,也就不會發生B方案中猜想的事情發生。

        也可以用另外一種方式防止Bean被回收的事件發生,那就是將當前新定義的Bean例項加入到ServletContext當中,這樣當前Bean例項就會全程被引用,所以不會被回收,這樣做相對比較可靠。