老司機帶你玩轉面試(5):Redis 集群模式 Redis Cluster


前文回顧

建議前面文章沒看過的同學先看下前面的文章:

「老司機帶你玩轉面試(1):緩存中間件 Redis 基礎知識以及數據持久化」

「老司機帶你玩轉面試(2):Redis 過期策略以及緩存雪崩、擊穿、穿透」

「老司機帶你玩轉面試(3):Redis 高可用之主從模式」

「老司機帶你玩轉面試(4):Redis 高可用之哨兵模式」

簡介

之前介紹的 Redis 的高可用方案:主從模式或者說哨兵模式,都只是在解決高可用的問題,比如說主從模式解決了讀高可用,哨兵模式解決了寫高可用。

如果我們需要緩存的數據量比較少,幾個 G 足夠用了,那么這兩種方案的高可用模式完全可以滿足需求,一個 master 對多個 salve ,需要幾個 salve 跟讀的吞吐量相關,然后再搞一個 sentinel 集群去保證 Redis 主從模式的高可用。

但是如果我們有大量的數據需要進緩存,單機的存儲容量無法滿足的時候,怎么辦?

這時,需要用到分布式緩存,就在幾年前,想用分布式緩存還得借助中間件來實現,比如當時風靡一時的 codis ,或者說還有 twemproxy 。在使用上,我們對 Redis 中間件進行讀寫操作, Redis 中間件負責將我們的數據分布式的存儲在多台機器上的 Redis 實例中。

而 Redis 也是不斷在發展的,終於在 3.0 的版本中(其實對現在來講也是比較早的版本了,現在的版本都 6.0 了),原生支持了集群模式。

可以做到在多台機器上,部署多個 Redis 實例,每個實例存儲一部分的數據,同時每個 Redis 主實例可以掛載 Redis 從實例,實現了 Redis 主實例掛了,會自動切換到 Redis 從實例上來。

除了實現了分布式存儲以外,還順便實現了高可用。

分布式尋址算法

Redis Cluster 的數據是分布式存儲的,這就必然會引發一個問題,假如我有 3 個節點,我如何知道一個 key 是存在這 3 個節點的哪一個節點上,取數的時候如何准確的找到對應的節點將數據取出來?這就要用到分布式尋址算法了。

常見的分布式尋址算法有兩種:

  • hash 算法
  • 一致性 hash 算法

hash 算法

hash 算法是對 key 進行 hash 運算后取值,然后對節點的數量取模。

接着將 key 存入對應的節點,但是,一旦其中某個節點宕機,所有的請求過來,都會基於最新的存活的節點數量進行取模運算,這就會導致大多數的請求無法拿到緩存數據。

舉個例子,一開始我有 3 個節點,這時大家正常的取模運算將數據基本均勻的存在了 3 個節點上,突然宕機了一台,現在只剩下 2 個有效的節點了,這時所有的運算都會基於最新的 2 個節點進行取模運算,原來的 key 對 2 進行取模運算后,顯然和對 3 進行取模運算得到的結果是不一樣的。

結果是大量的數據請求會直接打在 DB 上,這是不可容忍的。

一致性 hash 算法

一致性 hash 算法將整個 hash 值空間組織成一個虛擬的圓環,整個空間按順時針方向組織,下一步將各個 master 節點(使用服務器的 ip 或主機名)進行 hash。這樣就能確定每個節點在其哈希環上的位置。

來看一個經典的一致性 hash 算法的環狀示意圖:

來了一個 key,首先計算 hash 值,並確定此數據在環上的位置,從此位置沿環順時針「行走」,遇到的第一個 master 節點就是 key 所在位置。

在一致性哈希算法中,如果一個節點掛了,受影響的數據僅僅是此節點到環空間前一個節點(沿着逆時針方向行走遇到的第一個節點)之間的數據,其它不受影響。增加一個節點也同理。

一致性哈希算法在節點太少時,容易因為節點分布不均勻而造成緩存熱點的問題。為了解決這種熱點問題,一致性 hash 算法引入了虛擬節點機制,即對每一個節點計算多個 hash,每個計算結果位置都放置一個虛擬節點。這樣就實現了數據的均勻分布,負載均衡。

Redis Cluster 的 hash slot 算法

Redis Cluster 的實現方案十分的聰明,它的分區方式采用了虛擬槽分區。

Redis Cluster 首先會預設虛擬槽,每個槽就相當於一個數字,有一定范圍,每個槽映射一個數據子集。

Redis Cluster中預設虛擬槽的范圍為 0 到 16383

  1. Redis Cluster 會把 16384 個槽按照節點數量進行平均分配,由節點進行管理。
  2. 當一個 key 過來的時候,會對這個 key 按照 CRC16 規則進行 hash 運算。
  3. 把 hash 結果對 16383 進行取余。
  4. 把余數發送給 Redis 節點。
  5. 節點接收到數據,驗證是否在自己管理的槽編號的范圍:
    1. 如果在自己管理的槽編號范圍內,則把數據保存到數據槽中,然后返回執行結果。
    2. 如果在自己管理的槽編號范圍外,則會把數據發送給正確的節點,由正確的節點來把數據保存在對應的槽中。

注意:Redis Cluster 的節點之間會共享消息,每個節點都會知道是哪個節點負責哪個范圍內的數據槽

虛擬槽分布方式中,由於每個節點管理一部分數據槽,數據保存到數據槽中。當節點擴容或者縮容時,對數據槽進行重新分配遷移即可,數據不會丟失。

節點內部通信機制

在分布式的存儲方式中,還有另外一個點需要我們關注,那就是集群之間的內部通信方式,畢竟整個集群是需要知道當前集群中有多少有效的節點,這些節點分配的 ip 以及一些其他的數據,這些數據我們可以稱之為元數據。

集群元數據的維護有兩種方式:集中式、 Gossip 協議。而 Redis Cluster 節點間采用 Gossip 協議進行通信。

集中式是將集群元數據(節點信息、故障等等)幾種存儲在某個節點上。集中式元數據集中存儲的一個典型代表,就是大數據領域的 storm 。它是分布式的大數據實時計算引擎,是集中式的元數據存儲的結構,底層基於 zookeeper (分布式協調的中間件)對所有元數據進行存儲維護。

Redis 維護集群元數據采用另一個方式, Gossip 協議,所有節點都持有一份元數據,不同的節點如果出現了元數據的變更,就不斷將元數據發送給其它的節點,讓其它節點也進行元數據的變更。

Gossip 協議

Gossip 協議是一種非常有意思的協議,它的過程是由一個種子節點發起,它會隨機的選擇周圍幾個節點散播消息,收到消息的節點也會重復該過程,直至最終網絡中所有的節點都收到了消息。

這個過程可能需要一定的時間,由於不能保證某個時刻所有節點都收到消息,但是理論上最終所有節點都會收到消息,因此它是一個最終一致性協議。

Gossip 協議的一些特性:

  • 擴展性:網絡可以允許節點的任意增加和減少,新增加的節點的狀態最終會與其他節點一致。
  • 容錯:網絡中任何節點的宕機和重啟都不會影響 Gossip 消息的傳播,Gossip 協議具有天然的分布式系統容錯特性。
  • 去中心化: Gossip 協議不要求任何中心節點,所有節點都可以是對等的,任何一個節點無需知道整個網絡狀況,只要網絡是連通的,任意一個節點就可以把消息散播到全網。
  • 一致性收斂: Gossip 協議中的消息會以一傳十、十傳百一樣的指數級速度在網絡中快速傳播,因此系統狀態的不一致可以在很快的時間內收斂到一致。消息傳播速度達到了 logN。
  • 消息的延遲:由於 Gossip 協議中,節點只會隨機向少數幾個節點發送消息,消息最終是通過多個輪次的散播而到達全網的,因此使用 Gossip 協議會造成不可避免的消息延遲。不適合用在對實時性要求較高的場景下。
  • 消息冗余: Gossip 協議規定,節點會定期隨機選擇周圍節點發送消息,而收到消息的節點也會重復該步驟,因此就不可避免的存在消息重復發送給同一節點的情況,造成了消息的冗余,同時也增加了收到消息的節點的處理壓力。而且,由於是定期發送,因此,即使收到了消息的節點還會反復收到重復消息,加重了消息的冗余。

Gossip 協議包含多種消息,包含 ping , pong , meet , fail 等等。

  • meet:某個節點發送 meet 給新加入的節點,讓新節點加入集群中,然后新節點就會開始與其它節點進行通信。
  • ping:每個節點都會頻繁給其它節點發送 ping,其中包含自己的狀態還有自己維護的集群元數據,互相通過 ping 交換元數據。
  • pong:返回 ping 和 meeet,包含自己的狀態和其它信息,也用於信息廣播和更新。
  • fail:某個節點判斷另一個節點 fail 之后,就發送 fail 給其它節點,通知其它節點說,某個節點宕機啦。

由於 Redis Cluster 節點之間會定期交換 Gossip 消息,以及做一些心跳檢測,對網絡帶寬的壓力還有比較大的,包括官方都直接建議 Redis Cluster 節點數量不要超過 1000 個,當集群中節點數量過多的時候,會產生不容忽視的帶寬消耗。

高可用和主備切換

一說到集群就不得不提高可用和主備切換,而Redis cluster 的高可用的原理,幾乎跟哨兵是類似的。

判斷宕機

首先第一步當然是判斷宕機,這和哨兵模式非常的像,同樣是分成兩種類型,一種是主觀宕機 pfail ,另一種的客觀宕機 fail

  • 主觀宕機:一個節點認為另外一個節點宕機,這是一種「偏見」,並不代表整個集群的認知。
  1. 節點 1 定期發送 ping 消息給節點 2 。
  2. 如果發送成功,代表節點 2 正常運行,節點 2 會響應 PONG 消息給節點 1 ,節點 1 更新與節點 2 的最后通信時間
  3. 如果發送失敗,則節點 1 與節點 2 之間的通信異常判斷連接,在下一個定時任務周期時,仍然會與節點 2 發送 ping 消息
  4. 如果節點 1 發現與節點 2 最后通信時間超過 cluster-node-timeout ,則把節點 2 標識為 pfail 狀態。
  • 客觀宕機:當半數以上持有槽的主節點都標記某節點主觀下線。
  1. 當節點 1 認為節點 2 宕機后,那么會在 Gossip ping 消息中, ping 給其他節點。
  2. 當其他節點會重新嘗試之前節點 1 主觀宕機的過程。
  3. 如果有超過半數的節點都認為 pfail 了,那么這個節點 2 就會變成真的 fail

注意:集群模式下,只有主節點( master )才有讀寫權限和集群槽的維護權限,從節點( slave )只有復制的權限。

從節點選舉

  1. 先檢查資格,檢查所有的 salve 與 master 斷開連接的時間,如果超過了 cluster-node-timeout * cluster-slave-validity-factor ,那么久沒有資格成為 master 。
  2. 每個從節點,都根據自己對 master 復制數據的 offset,來設置一個選舉時間,offset 越大(復制數據越多)的從節點,選舉時間越靠前,優先進行選舉。
  3. 所有的 master 開始選舉投票,如果大部分 master 節點 (N/2 + 1) 都投票給了某個從節點,那么選舉通過,那個從節點可以切換成 master 。
  4. 從節點執行主備切換,從節點切換為主節點。

參考

https://www.cnblogs.com/williamjie/p/11132211.html

https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-cluster.md

https://zhuanlan.zhihu.com/p/41228196


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM