1. 程式人生 > >老司機帶你玩轉面試(5):Redis 叢集模式 Redis Cluster

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

![](https://cdn.geekdigging.com/Interview/mianshi_header_1.jpg) ## 前文回顧 建議前面文章沒看過的同學先看下前面的文章: [「老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化」](https://www.geekdigging.com/2020/07/12/2878870968/) [「老司機帶你玩轉面試(2):Redis 過期策略以及快取雪崩、擊穿、穿透」](https://www.geekdigging.com/2020/07/12/1506238325/) [「老司機帶你玩轉面試(3):Redis 高可用之主從模式」](https://www.geekdigging.com/2020/07/15/3004256369/) [「老司機帶你玩轉面試(4):Redis 高可用之哨兵模式」](https://www.geekdigging.com/2020/07/16/8662209381/) ## 簡介 之前介紹的 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 演算法的環狀示意圖: ![](https://cdn.geekdigging.com/Interview/consistent-hashing-algorithm.png) 來了一個 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` (分散式協調的中介軟體)對所有元資料進行儲存維護。 ![](https://cdn.geekdigging.com/Interview/zookeeper-centralized-storage.png) Redis 維護叢集元資料採用另一個方式, `Gossip` 協議,所有節點都持有一份元資料,不同的節點如果出現了元資料的變更,就不斷將元資料傳送給其它的節點,讓其它節點也進行元資料的變更。 ![](https://cdn.geekdigging.com/Interview/redis-gossip.png) ### Gossip 協議 Gossip 協議是一種非常有意思的協議,它的過程是由一個種子節點發起,它會隨機的選擇周圍幾個節點散播訊息,收到訊息的節點也會重複該過程,直至最終網路中所有的節點都收到了訊息。 這個過程可能需要一定的時間,由於不能保證某個時刻所有節點都收到訊息,但是理論上最終所有節點都會收到訊息,因此它是一個最終一致性協議。 ![](https://cdn.geekdigging.com/Interview/gossip_demo.gif) 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/