Redis Cluster 的實現原理

引言

在redis cluster出現之前,通常通過以下幾種方式搭建redis集群:

  1. 客戶端分片
  2. 這種方案將分片工作放在業務程序端,程序代碼根據預先設置的路由規則,直接對多個Redis實例進行分佈式訪問。這樣的好處是,不依賴於第三方分佈式中間件,實現方法和代碼都自己掌控,可隨時調整,不用擔心踩到坑。
  3. 這實際上是一種靜態分片技術。Redis實例的增減,都得手工調整分片程序。基於此分片機制的開源產品,現在仍不多見。
  4. 這種分片機制的性能比代理式更好(少了一箇中間分發環節)。但缺點是升級麻煩,對研發人員的個人依賴性強——需要有較強的程序開發能力做後盾。如果主力程序員離職,可能新的負責人,會選擇重寫一遍。
  5. 所以,這種方式下,可運維性較差。出現故障,定位和解決都得研發和運維配合著解決,故障時間變長。
  6. 代理分片
  7. 這種方案,將分片工作交給專門的代理程序來做。代理程序接收到來自業務程序的數據請求,根據路由規則,將這些請求分發給正確的Redis實例並返回給業務程序。
  8. 這種機制下,一般會選用第三方代理程序(而不是自己研發),因為後端有多個Redis實例,所以這類程序又稱為分佈式中間件。
  9. 這樣的好處是,業務程序不用關心後端Redis實例,運維起來也方便。雖然會因此帶來些性能損耗,但對於Redis這種內存讀寫型應用,相對而言是能容忍的。
  10. 這是我們推薦的集群實現方案。像基於該機制的開源產品Twemproxy,便是其中代表之一,應用非常廣泛。

認識Redis Cluster

Redis Cluster是由多個同時服務於一個數據集合的Redis實例組成的整體,對於用戶來說,用戶只關注這個數據集合,而整個數據集合的某個數據子集存儲在哪個節點對於用戶來說是透明的。Redis Cluster具有分佈式系統的特點,也具有分佈式系統如何實現高可用性與數據一致性的難點

Redis Cluster 的實現原理

Redis Cluster特點如下:

  1. 所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬。
  2. 節點的fail是通過集群中超過半數的節點檢測失效時才生效。
  3. 客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可。
  4. redis-cluster把所有的物理節點映射到[0-16383]slot上(不一定是平均分配),cluster 負責維護nodeslotvalue。
  5. Redis集群預分好16384個桶,當需要在 Redis 集群中放置一個 key-value 時,根據 CRC16(key) mod 16384的值,決定將一個key放到哪個桶中。

Redis Cluster搭建

集群中至少應該有奇數個節點,所以至少有三個節點,每個節點至少有一個備份節點,所以下面使用6節點(主節點、備份節點由redis-cluster集群確定)。

1.進入到redis目錄下,配置redis的配置文件redis.conf

daemonize yes #後臺啟動

port 7001 #修改端口號,從7001到7006

cluster-enabled yes #開啟cluster,去掉註釋

cluster-config-file nodes.conf

cluster-node-timeout 15000

appendonly yes

2.將redis.conf 配置文件複製6份,分別修改對應的端口,6個文件如下:redis_7001.conf,redis_7002.conf,redis_7003.conf,redis_7004.conf,redis_7006.conf

3.安裝redis-trib所需的 ruby腳本

yum install ruby

yum install rubygems

gem install redis-xxx.gem

4.啟動redis服務

./redis-server ../redis_7001.conf

./redis-server ../redis_7002.conf

./redis-server ../redis_7003.conf

./redis-server ../redis_7004.conf

./redis-server ../redis_7005.conf

./redis-server ../redis_7006.conf

5.創建redis-cluster

redis-trib.rb create --replicas 1 127.0.0.1:6310 127.0.0.1:6320 127.0.0.1:6330 127.0.0.1:6340 127.0.0.1:6350 127.0.0.1:6360

使用create命令 --replicas 1 參數表示為每個主節點創建一個從節點,其他參數是實例的地址集合。

6.redis集群的測試

客戶端連接集群redis-cli需要帶上 -c ,redis-cli -c -p 端口號

./redis-cli -c -p 7001

127.0.0.1:7001> set name andy

-> Redirected to slot [5798] located at 127.0.0.1:7002

OK

127.0.0.1:7002> get name

"andy"

127.0.0.1:7002>

根據redis-cluster的key值分配,name應該分配到節點7002[5461-10922]上,上面顯示redis cluster自動從7001跳轉到了7002節點。

我們可以測試一下7006從節點獲取name值,至此我們redis的集群就成功搭建了。

Redis Cluster實現原理

Redis Cluster 的原理其實也很簡單,用一張大圖概括如下:

Redis Cluster 的實現原理

  1. 對象保存到Redis之前先經過CRC16哈希到一個指定的Node上,例如Object4最終Hash到了Node1上。
  2. 每個Node被平均分配了一個Slot段,對應著0-16383,Slot不能重複也不能缺失,否則會導致對象重複存儲或無法存儲。
  3. Node之間也互相監聽,一旦有Node退出或者加入,會按照Slot為單位做數據的遷移。例如Node1如果掉線了,0-5640這些Slot將會平均分攤到剩餘node上,由於剩餘node本身維護的Slot還會在自己身上不會被重新分配,所以遷移過程中不會影響到5641-16383Slot段的使用。

1. 槽(slot)概念

redis cluster引入槽的概念,一定要與一致性hash的槽區分!這裡每一個槽映射一個數據集。

CRC16(key) & 16384

這裡計算結果發送給redis cluster任意一個redis節點,這個redis節點發現他是屬於自己管轄範圍的,那就將它放進去;不屬於他的槽範圍的話,由於redis之間是相互通信的,這個節點是知道其他redis節點的槽的信息,那麼會告訴他去那個redis節點去看看,這樣就實現了服務端對於槽、節點、數據的管理。

當redis集群需要擴容的時候,由於每個節點維護的槽的範圍是固定的,當有新加入的節點時,是不會干擾到其他節點的槽的,必須是以前的節點將使用槽的權利分配給你,並且將數據分配給你,這樣,新的節點才會真正擁有這些槽和數據。這種實現還處於半自動狀態,需要人工介入。主要的思想是:槽到集群節點的映射關係要改變,不變的是鍵到槽的映射關係。

Redis集群要保證16384個槽對應的node都正常工作,如果某個node發生故障,那它負責的slots也就失效,整個集群將不能工作。為了增加集群的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,如果主節點失效,Redis Cluster會根據選舉算法從slave節點中選擇一個上升為主節點,整個集群繼續對外提供服務。

2. 位序列結構

某個Master是怎麼知道某個槽自己是不是擁有呢?Master節點維護著一個16384/8字節的位序列,Master節點用bit來標識對於某個槽自己是否擁有。比如對於編號為1的槽,Master只要判斷序列的第二位(索引從0開始)是不是為1即可。

Redis Cluster 的實現原理

如上面的序列,表示當前Master擁有編號為1,101,102的槽。集群同時還維護著槽到集群節點的映射,是由長度為16384類型為節點的數組實現的,槽編號為數組的下標,數組內容為集群節點,這樣就可以很快地通過槽編號找到負責這個槽的節點。位序列這個結構很精巧,即不浪費存儲空間,操作起來又很便捷。

redis節點之間如何通信的?

Redis Cluster 的實現原理

  1. gossip協議:節點之間彼此不斷通信交換信息,一段時間後所有節點都會知道集群完整的信息。
  2. 節點與節點之間通過二進制協議進行通信。
  3. 客戶端和集群節點之間通信和通常一樣,通過文本協議進行。
  4. 集群節點不會代理查詢。

3. slot 數據遷移

這裡6382為新加入的節點,一開始是沒有槽的,所以進行slot的遷移。

Redis Cluster 的實現原理

遷移數據的流程圖:

Redis Cluster 的實現原理

槽遷移的過程中有一個不穩定狀態,這個不穩定狀態會有一些規則,這些規則定義客戶端的行為,從而使得Redis Cluster不必宕機的情況下可以執行槽的遷移。

MIGRATING狀態

預備遷移槽的時候槽的狀態首先會變為MIGRATING狀態,這種狀態的槽會實際產生什麼影響呢?當客戶端請求的某個Key所屬的槽處於MIGRATING狀態的時候,影響有下面幾條:

  1. 如果Key存在則成功處理
  2. 如果Key不存在,則返回客戶端ASK,僅當這次請求會轉向另一個節點,並不會刷新客戶端中node的映射關係,也就是說下次該客戶端請求該Key的時候,還會選擇MasterA節點如果Key包含多個命令,如果都存在則成功處理,如果都不存在,則返回客戶端ASK,如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍後重試,這樣當所有的Key都遷移完畢的時候客戶端重試請求的時候回得到ASK,然後經過一次重定向就可以獲取這批鍵
IMPORTING狀態

槽從MasterA節點遷移到MasterB節點的時候,槽的狀態會首先變為IMPORTING。IMPORTING狀態的槽對客戶端的行為有下面一些影響:

正常命令會被MOVED重定向,如果是ASKING命令則命令會被執行,從而Key沒有在老的節點已經被遷移到新的節點的情況可以被順利處理;如果Key不存在則新建;沒有ASKING的請求和正常請求一樣被MOVED,這保證客戶端node映射關係出錯的情況下不會發生寫錯;

4. 客戶端路由

moved重定向
Redis Cluster 的實現原理

其中,槽直接命中的話,就直接返回槽編號:

Redis Cluster 的實現原理

槽不命中,返回帶提示信息的異常,客戶端需要重新發送一條命令:

Redis Cluster 的實現原理

ask重定向

在擴容縮容的時候,由於需要遍歷這個節點上的所有的key然後進行遷移,是比較慢的,對客戶端是一個挑戰。因為假設一個場景,客戶端訪問某個key,節點告訴客戶端這個key在源節點,當我們再去源節點訪問的時候,卻發現key已經遷移到目標節點.

moved重定向和ask重定向對比
  1. 兩者都是客戶端單重定向
  2. moved:槽已經確定轉移
  3. ask:槽還在遷移中
  4. 問題:如果節點眾多,那麼讓客戶端隨機訪問節點,那麼直接命中的概率只有百分之一,還有就是發生ask異常時(即節點正在遷移時)客戶端如何還能高效運轉?
  5. 總結一句話就是redis cluster的客戶端的實現會更復雜。


分享到:


相關文章: