Jedis集成與踩坑經歷

Jedis簡介

Jedis是Redis的Java客戶端實現,封裝了對Redis的通訊和命令處理等。html

Jedis提供了資源池,能夠很方便地實現對Redis的API調用。java

Jedis集成

目標

以前是經過組內對Jedis封裝的Spring Bean來獲取和使用Jedis的,如今但願自行實現相似功能,設計目標以下:nginx

  1. 封裝爲Spring FactoryBean
  2. 集成目前自行實現的基於ZooKeeper的配置中心組件
  3. 獲取單個鏈接,避免每次調用須要從資源池中獲取一個鏈接的額外操做(其實還有歸還操做)
  4. 實如今服務啓動依賴注入後配置變動仍然能生效

思路

具體思路就是針對設計目標而定的:git

  1. 實現FactoryBean
  2. 接入Config組件
  3. DCL單例生成資源池JedisPool,並經過動態代理提供Jedis鏈接,鏈接從JedisPool中獲取
  4. 配置變動後使用新的JedisPool替換舊的

具體實現

因爲需求比較基礎,尚未太多應用場景,實現也沒考慮太複雜。總體邏輯不到50行,能夠在個人GitHub上大體看一下。github

後續使用能夠直接使用Spring將Bean注入。redis

Jedis踩坑

因爲不按常規方法使用JedisPool可能背離了JedisPool設計的使用場景,所以在其中踩了很多坑。瀏覽器

其次,雖然日常經常使用組內的Jedis組件,但實際上對Jedis的API不瞭解,本次根據日常使用過程當中的一些感覺進行「黑盒臨摹」,在爬坑過程當中其實也學習了其餘一些方面的經驗,好比Guava Reflections等。安全

坑#1 併發異常

背景微信

最開始經過FactoryBean提供的鏈接並未使用動態代理,也就是說僅提供了一個Jedis,全部線程使用同一個Jedis鏈接。多線程

現象

業務中較頻繁地報異常,異常信息爲java.lang.ClassCastException: java.util.ArrayList cannot be cast to [B等,基本是ClassCastException,異常拋出位置爲調用Jedis的位置。

  • 初看覺得是Jedis的泛型bug,但不是必現,能夠排除這種猜想;
  • 有人認爲是對於Redis的響應命令處理不完善,如OKNIL等響應,但這種bug過低級了,不該該出如今Jedis這種久經考驗的庫中,排除;
  • 有人認爲發生異常狀況時Jedis的buffer未清空,致使後續使用中殘存以前的錯誤數據,這個卻是有點道理,由於這種狀況不是必現,但同上一條,不該該出如今這種庫中;

緣由

最終在另外一篇資料指引下來到jedis/issues,在參考資料中發現了最可信最合理的緣由:Jedis並不是線程安全,不該當併發操做。

正確使用

Single connection. Single thread.

正如參考資料中回答提到的,每一個線程(每次調用)都從JedisPool中獲取一個鏈接,並在使用後歸還。

也正是由於這一點跟最初的FactoryBean封裝方式衝突了,後來才改用提供動態代理類的方式封裝FactoryBean。

坑#2 須要手動歸還鏈接

背景

我使用的Jedis版本爲3.0.1,網上的很多資料指出在使用鏈接後歸還可使用JedisPool的void returnResource(Jedis resource)方法,但在3.0.1版本中這個方法是protected可見的,沒有間接調用方法。

另外Jedis源碼中找不到註釋,這有點奇怪,我想固然地認爲版本升級後能夠自動歸還資源了,因而僅在設置最大鏈接數以後就部署到業務中了。

現象

業務線程啓動後每訪問必定次數(調用Jedis達到必定次數)後就徹底不響應請求了:

  • 後續全部請求超時無響應
  • CPU正常,內存正常
  • Java線程有很多處於等待狀態,GC正常,但從日誌看沒有任何請求到達Servlet
  • Nginx(用於反向代理)日誌中響應狀態碼是499
    • 499是Nginx自定義的,對應的是client has closed connection即用戶瀏覽器斷開Http鏈接了

仍是在參考資料的指引下查看Tomcat監聽的端口,的確不少鏈接處於CLOSE_WAIT狀態,代表客戶端已斷開鏈接(我本身測試的時候刷新頁面太多,不少就中途斷開了)。

緣由

結合TCP四次揮手過程,應該是中間有資源釋放不了致使沒有進入LAST_ACK狀態,推測是Jedis鏈接資源未歸還而總鏈接數有限制,致使不少線程在等待獲取Jedis資源。

正確使用

在Jedis鏈接使用完畢後,須要調用Jedis的close()方法將資源歸還JedisPool,close方法是用於替代returnResource方法的。

這個方法語義比較奇怪,真實做用是「歸還或關閉鏈接」,最開始其實就是考慮到資源複用的問題纔沒有考慮使用這個close方法的。

能夠優化的點

對比了一下組內的組件,思路差很少,還有如下的點可以擴展:

  1. 多種資源池的兼容性
  2. 接入監控上報

參考資料

ClassCastException - B cannot be cast to java.lang.Long · Issue #186 · xetorthio/jedis

nginx 499狀態碼 - 微信-大數據從業者 - 博客園

深刻分析Tomcat無響應問題及解決方法 - 月下狼的我的頁面 - OSCHINA

jedis:鏈接池(JedisPool)使用示例 - 10km的專欄 - CSDN博客

Jedis使用|returnBrokenResource|returnResource廢棄替代 - 詩人不寫詩 - CSDN博客

本文搬自個人博客,歡迎參觀!

相關文章
相關標籤/搜索