前文回顧
建議前面文章沒看過的同學先看下前面的文章:
「老司機帶你玩轉面試(1):緩存中間件 Redis 基礎知識以及數據持久化」
「老司機帶你玩轉面試(2):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
- Redis Cluster 會把 16384 個槽按照節點數量進行平均分配,由節點進行管理。
- 當一個 key 過來的時候,會對這個 key 按照 CRC16 規則進行 hash 運算。
- 把 hash 結果對 16383 進行取余。
- 把余數發送給 Redis 節點。
- 節點接收到數據,驗證是否在自己管理的槽編號的范圍:
- 如果在自己管理的槽編號范圍內,則把數據保存到數據槽中,然后返回執行結果。
- 如果在自己管理的槽編號范圍外,則會把數據發送給正確的節點,由正確的節點來把數據保存在對應的槽中。
注意: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 定期發送 ping 消息給節點 2 。
- 如果發送成功,代表節點 2 正常運行,節點 2 會響應 PONG 消息給節點 1 ,節點 1 更新與節點 2 的最后通信時間
- 如果發送失敗,則節點 1 與節點 2 之間的通信異常判斷連接,在下一個定時任務周期時,仍然會與節點 2 發送 ping 消息
- 如果節點 1 發現與節點 2 最后通信時間超過
cluster-node-timeout
,則把節點 2 標識為 pfail 狀態。
- 客觀宕機:當半數以上持有槽的主節點都標記某節點主觀下線。
- 當節點 1 認為節點 2 宕機后,那么會在 Gossip ping 消息中, ping 給其他節點。
- 當其他節點會重新嘗試之前節點 1 主觀宕機的過程。
- 如果有超過半數的節點都認為
pfail
了,那么這個節點 2 就會變成真的fail
。
注意:集群模式下,只有主節點( master )才有讀寫權限和集群槽的維護權限,從節點( slave )只有復制的權限。
從節點選舉
- 先檢查資格,檢查所有的 salve 與 master 斷開連接的時間,如果超過了
cluster-node-timeout * cluster-slave-validity-factor
,那么久沒有資格成為 master 。 - 每個從節點,都根據自己對 master 復制數據的 offset,來設置一個選舉時間,offset 越大(復制數據越多)的從節點,選舉時間越靠前,優先進行選舉。
- 所有的 master 開始選舉投票,如果大部分 master 節點 (N/2 + 1) 都投票給了某個從節點,那么選舉通過,那個從節點可以切換成 master 。
- 從節點執行主備切換,從節點切換為主節點。
參考
https://www.cnblogs.com/williamjie/p/11132211.html
https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/redis-cluster.md