導讀:
Redis官方號稱支持并發11萬讀操作,并發8萬寫操作。由于優異的性能和方便的操作,相信很多人都在項目中都使用了Redis,為了不讓應用過分的依賴 Redis服務,Redis的作用只作為提升應用并發和降低應用響應時間存在,即使Redis出現異常,應用程序也不應該出現提供服務失敗問題,對此拍拍信最近安排了一次全環境的Redis Cluster 宕機演練。
本文作者系拍拍信架構負責人朱榮松和拍拍信架構開發工程師許彬,授權“技術鎖話”進行發布。
Redis 集群環境:
1. 測試環境:
Redis Cluster 配置 :Redis 3主 3從 一共6個節點。
2. 預發環境:
Redis Cluster 配置 :Redis 3主 3從 一共6個節點。
下面是我們操作的時間線:
第一天
程序運行中關閉任意一臺從節點,測試一天均無異常。
第二天
程序運行中關閉任意一臺從節點,程序未發現異常,測試一天未發現異常。
第三天
預發環境有應用發版,出現異常程序無法啟動。
……
1. 測試與預發環境目前關閉的都是任意一臺Redis從節點。
2. 測試環境經過反復測試無問題才開始關閉預發環境節點。
3. 預發環境重啟被關閉的Redis節點后異常消失。
4. 連接Redis客戶端使用的是Java語言中使用范圍較廣的Jedis。
那么為什么測試環境在經過反復測試沒有問題,到預發環境會出現問題?
分析問題前先簡單解釋下Redis Cluster實現原理。簡單來說Redis Cluster中內置了 16384 個哈希槽,當需要在 Redis Cluster中存取一個 key或者value時,Redis 客戶端先對 key 使用 crc16 算法算出一個結果,然后把結果對 16384 求余數( 算法為:crc16(key)mod 16384),這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,值得注意的是這個計算key是在哪個槽上的操作是Redis 客戶端做的操作,Java中常用的客戶端為Jedis 這個也是被Spring推薦的一種客戶端。
注: 如果有人好奇為什么Redis Cluster為什么會使用16384也就是2^14個槽??梢圆榭?Github https://github.com/antirez/redis/issues/2576作者對此進行了解釋。
圖1異常很明顯拋出的是連接異常
查看了Jedis的源碼后發現初始化Redis Cluster的槽信息時,調用initializeSlotsCache()方法時出現異常。圖2 為此方法的具體實現,分析代碼發現此代碼的目的應該是需要cache Redis Cluster槽信息,由于代碼中有break,所以是只需要連接Redis獲取一次信息即可。細一看此代碼應該是有Bug,Try 的范圍沒有覆蓋到Jedis連接的操作,如果Jedis連接失敗直接拋出連接失敗異常,此循環會直接退出,與代碼實際預期不符合。
圖2
由此引發另一個思考,是不是我關閉的節點正好為循環的第一個節點導致此問題。嘗試關閉另外一臺從節點后程序正常啟動。那么Jedis加載的節點順序是什么,似乎Jedis對節點順序進行了排序操作。在查看源碼后發現Jedis重寫了Redis節點配置類的hashCode方法。
圖3
圖4
下面簡單測試下如果配置為:jedis-01.test.com、jedis-02.test.com、jedis-03.test.com、jedis-04.test.com、jedis-05.test.com、jedis-05.test.com輸出順序是什么。
圖5
輸出結果:
[redis-06.test.com:6379,redis-04.test.com:6379, redis-01.test.com:6379, redis-03.test.com:6379, redis-02.test.com:6379,redis-05.test.com:6379]
也就是說如果關閉redis-06.test.com:6379這臺節點,程序就會出現啟動失敗問題。
問題定位后首先去Github上的查看相關問題是否有人遇到,在查詢后發現此問題有人在去年11月提了PR解決了此問題,鏈接如下:
https://github.com/xetorthio/jedis/pull/1633
官方目前釋放出了2.10.0-m1和3.0.0-m1中解決了此問題,但是由于不是Release版本使用還得注意。解決的辦法為圖6,和圖2對比可以發現圖6對Jedis的實例化也進行了try catch。
圖6
六、思考
圖7
那么問題來了多少節點異常會導致程序讀寫操作出現異常,下面我們也做了個簡單的測試用于統計程序運行中,關閉Redis節點后程序的出錯情況,以下測試表1僅供參考。
場景 | 操作(多節點均同時操作) | Redis寫總量 | Redis讀總量 | 錯誤量 | 總耗時(s) | 錯誤率 |
程序運行中 | 關主(關任一主) | 100000 | 100000 | 3084 | 100 | 0.031 |
關主(關任一主) | 100000 | 100000 | 1482 | 102 | 0.015 | |
關主(關任一主) | 100000 | 100000 | 3053 | 97.6 | 0.031 | |
關從(關任一從) | 100000 | 100000 | 0 | 109.2 | 0 | |
關從(關任一從) | 100000 | 100000 | 0 | 90.1 | 0 | |
關從(關任一從) | 100000 | 100000 | 0 | 88.9 | 0 | |
主從一起關(關任一對) | 100000 | 100000 | 32613 | 210.1 | 0.326 | |
主從一起關(關任一對) | 100000 | 100000 | 29148 | 169.8 | 0.291 | |
主從一起關(關任一對) | 100000 | 100000 | 32410 | 173.7 | 0.324 | |
所有主全關 | 100000 | 100000 | 100000 | 353.4 | 1 | |
所有從全關 | 100000 | 100000 | 0 | 87.7 | 0 | |
只留一臺主 | 100000 | 100000 | 100000 | 357.1 | 1 |
表1
從測試結果看,集群Master的選舉過程是由Master參與選舉的。
1. 如果半數以上 Master 處于關閉狀態那么整個集群處于不可用狀態。
2. 關閉任意一對主從節點會導致部分(大約為整個集群的1/3)失敗。
3. 關閉任意一主,會導致部分寫操作失敗,是由于從節點不能執行寫操作,在Slave升級為Master期間會有少量的失敗。
4. 關閉從節點對于整個集群沒有影響。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。