1. 程式人生 > >Redis Cluster流程原理

Redis Cluster流程原理

Redis 3.0之後,節點之間通過去中心化的方式,提供了完整的shardingreplication(複製機制仍使用原有機制,並且具備感知主備的能力)、failover解決方案,稱為Redis Cluster。即:將proxy/sentinel的工作融合到了普通Redis節點裡。後面將介紹Redis Cluster這種模式下,水平拆分、故障轉移等需求的實現方式。

拓撲結構

一個Redis Cluster由多個Redis節點組成。不同的節點組服務的資料無交集,每個節點對應資料sharding的一個分片。節點組內部分為主備2類,對應前面敘述的master和slave。兩者資料準實時一致,通過非同步化的主備複製機制保證。一個節點組有且僅有一個master,同時有0到多個slave。只有master對外提供寫服務,讀服務可由master/slave提供。如下所示:

這裡寫圖片描述

上圖中,key-value全集被分成了5份,5個slot(實際上Redis Cluster有 16384 [0-16383] 個slot,每個節點服務一段區間的slot,這裡面僅僅舉例)。A和B為master節點,對外提供寫服務。分別負責1/2/3和4/5的slot。A/A1和B/B1/B2之間通過主備複製的方式同步資料。

上述的5個節點,兩兩通過Redis Cluster Bus互動,相互交換如下的資訊:

  • 資料分片(slot)和節點的對應關係;
  • 叢集中每個節點可用狀態;
  • 叢集結構發生變更時,通過一定的協議對配置資訊達成一致。資料分片的遷移、主備切換、單點master的發現和其發生主備關係變更等,都會導致叢集結構變化。
  • publish/subscribe(釋出訂閱)功能,在Cluster版內部實現所需要互動的資訊。

Redis Cluster Bus通過單獨的埠進行連線,由於Bus是節點間的內部通訊機制,互動的是位元組序列化資訊。相對Client的字元序列化來說,效率較高。

Redis Cluster是一個去中心化的分散式實現方案,客戶端和叢集中任一節點連線,然後通過後面的互動流程,逐漸的得到全域性的資料分片對映關係。

配置的一致性

對於去中心化的實現,叢集的拓撲結構並不儲存在單獨的配置節點上,後者的引入同樣會帶來新的一致性問題。那麼孤立的節點間,如何對叢集的拓撲達成一致,是Redis Cluster配置機制要解決的問題。Redis Cluster通過引入2個自增的Epoch變數

,來使得叢集配置在各個節點間最終達成一致。

1、配置資訊資料結構

Redis Cluster中的每個節點都儲存了叢集的配置資訊,並且儲存在clusterState中,結構如下:

這裡寫圖片描述

上圖的各個變數語義如下:

  • clusterState 記錄了從叢集中某個節點視角,來看叢集配置狀態;
  • currentEpoch 表示整個叢集中最大的版本號,叢集資訊每變更一次,改版本號都會自增。
  • nodes 是一個列表,包含了本節點所感知的,叢集所有節點的資訊(clusterNode),也包含自身的資訊。
  • clusterNode 記錄了每個節點的資訊,其中包含了節點本身的版本 Epoch;自身的資訊描述:節點對應的資料分片範圍(slot)、為master時的slave列表、為slave時的master等。

每個節點包含一個全域性唯一的NodeId

當叢集的資料分片資訊發生變更(資料在節點間遷移時),Redis Cluster 仍然保持對外服務。

當叢集中某個master出現宕機時,Redis Cluster 會自動發現,並觸發故障轉移的操作。會將master的某個slave晉升為新的 master。

由此可見,每個節點都儲存著Node視角的叢集結構。它描述了資料的分片方式,節點主備關係,並通過Epoch 作為版本號實現叢集結構資訊的一致性,同時也控制著資料遷移和故障轉移的過程。

2、資訊互動

去中心化的架構不存在統一的配置中心。在Redis Cluster中,這個配置資訊互動通過Redis Cluster Bus來完成(獨立埠)。Redis Cluster Bus 上互動的資訊結構如下:

這裡寫圖片描述

clusterMsg 中的type指明瞭訊息的型別,配置資訊的一致性主要依靠PING/PONG。每個節點向其他節點頻繁的週期性的傳送PING/PONG訊息。對於訊息體中的Gossip部分,包含了sender/receiver 所感知的其他節點資訊,接受者根據這些Gossip 跟新對叢集的認識。

對於大規模的叢集,如果每次PING/PONG 都攜帶著所有節點的資訊,則網路開銷會很大。此時Redis Cluster 在每次PING/PONG,只包含了隨機的一部分節點資訊。由於互動比較頻繁,短時間的幾次互動之後,叢集的狀態也會達成一致。

3、一致性的達成

當Cluster 結構不發生變化時,各個節點通過gossip 協議在幾輪互動之後,便可以得知Cluster的結構資訊,達到一致性的狀態。但是當叢集結構發生變化時(故障轉移/分片遷移等),優先得知變更的節點通過Epoch變數,將自己的最新資訊擴散到Cluster,並最終達到一致。

  • clusterNode 的Epoch描述的單個節點的資訊版本;
  • clusterState 的currentEpoch 描述的是叢集資訊的版本,它可以輔助Epoch 的自增生成。因為currentEpoch 是維護在每個節點上的,在叢集結構發生變更時,Cluster 在一定的時間視窗控制更新規則,來保證每個節點的currentEpoch都是最新的。

更新規則如下:

  1. 當某個節點率先知道了變更時,將自身的currentEpoch 自增,並使之成為叢集中的最大值。再用自增後的currentEpoch 作為新的Epoch 版本;
  2. 當某個節點收到了比自己大的currentEpoch時,更新自己的currentEpoch;
  3. 當收到的Redis Cluster Bus 訊息中的某個節點的Epoch > 自身的時,將更新自身的內容;
  4. 當Redis Cluster Bus 訊息中,包含了自己沒有的節點時,將其加入到自身的配置中。

上述的規則保證了資訊的更新都是單向的,最終朝著Epoch更大的資訊收斂。同時Epoch也隨著currentEpoch的增加而增加,最終將各節點資訊趨於穩定。

sharding

不同節點分組服務於相互無交集的分片(sharding),Redis Cluster 不存在單獨的proxy或配置伺服器,所以需要將客戶端路由到目標的分片。

1、資料分片(slot)

Redis Cluster 將所有的資料劃分為16384 [0-16383] 個分片,每個分片負責其中一部分。每一條資料(key-value)根據key值通過資料分佈演算法(一致性雜湊)對映到16384 個slot中的一個。資料分佈演算法為:

slotId = crc16(key) % 16384

客戶端根據slotId 決定將請求路由到哪個Redis 節點。Cluster 不支援跨節點的單命令,如:sinterstore,如果涉及的2個key對應的slot 在不同的Node,則執行失敗。通常Redis的key都是帶有業務意義的,如:Product:Trade:20180890310921230001Product:Detail:20180890310921230001。當在叢集中儲存時,上述同一商品的交易和詳情可能會儲存在不同的節點上,進而對於這2個key 不能以原子的方式操作。為此,Redis引入了HashTag的概念,使得資料分佈演算法可以根據key 的某一部分進行計算,讓相關的2 條記錄落到同一個資料分片。如:

  • 商品交易記錄key:Product:Trade:{20180890310921230001}
  • 商品詳情記錄key:Product:Detail:{20180890310921230001}

Redis 會根據 {} 之間的字串作為資料分散式演算法的輸入。

2、客戶端的路由

Redis Cluster的客戶端相比單機Redis 需要具備路由語義的識別能力,且具備一定的路由快取能力。當Client 訪問的key 不在當前Redis 節點的slots中,Redis 會返回給Client 一個moved命令。並告知其正確的路由資訊,如下所示:

這裡寫圖片描述

當Client 接收到moved 後,再次請求新的Redis時,此時Cluster 的結構又可能發生了變化。此時有可能再次返回moved 。Client 會根據moved響應,更新其內部的路由快取資訊,以便後續的操作直接找到正確的節點,減少互動次數。

當Cluster 在資料重新分佈過程中時,可以通過ask 命令控制客戶端的路由,如下所示:

這裡寫圖片描述

上圖中,slot 1 需要遷移到新節點上,此時如果客戶端已經完成遷移的key,節點將相應ask 告知客戶端想目標節點重試。

ask命令和moved 命令的不同在於,moved 會更新Client資料路由,ask 只是重定向新節點,但是後續的相同slot 仍會路由到舊節點。

遷移的過程可能會持續一段時間,這段時間某個slot 的資料,同時可能存在於新舊 2 個節點。由於move 操作會使Client 的路由快取變更,如果新舊節點對於遷移中的slot 所有key 都回應moved,客戶端的路由快取會頻繁變更。因此引入ask 型別訊息,將重定向和路由快取分離

3、分片的遷移

在一個穩定的Redis Cluster 中,每個slot 對應的節點都是確定的。在某些情況下,節點和分片需要變更:

  • 新的節點作為master加入;
  • 某個節點分組需要下線;
  • 負載不均衡需要調整slot 分佈。

此時需要進行分片的遷移,遷移的觸發和過程控制由外部系統完成。Redis Cluster 只提供遷移過程中需要的原語,包含下面 2 種:

  • 節點遷移狀態設定:遷移前標記源/目標節點。
  • key遷移的原子化命令:遷移的具體步驟。

下面的Demo會介紹slot 1 從節點A 遷移到B的過程。

這裡寫圖片描述

  1. 向節點B傳送狀態變更命令,將B的對應slot 狀態置為importing
  2. 向節點A傳送狀態變更命令,將A對應的slot 狀態置為migrating
  3. 針對A上的slot 的所有key,分別向A 傳送migrate 命令,告知A 將對應的key 遷移到B。

當A節點的狀態置為migrating 後,表示對應的slot 正在從A遷出,為保證該slot 資料的一致性。A此時提供的寫服務和通常狀態下有所區別,對於某個遷移中的slot:

  • 如果Client 訪問的key 尚未遷出,則正常的處理該key;
  • 如果key已經遷出或者key不存在,則回覆Client ASK 資訊讓其跳轉到B處理;

當節點B 狀態變成importing 後,表示對應的slot 正在向B遷入。即使B 能對外提供該slot 的讀寫服務,但是和通常情況下有所區別:

  • 當Client的訪問不是從ask 跳轉的,說明Client 還不知道遷移。有可能操作了尚未遷移完成的,處在A上面的key,如果這個key 在A上被修改了,則後續會產生衝突。
  • 所以對於該slot 上所有非ask 跳轉的操作,B不會進行操作,而是通過moved 讓Client 跳轉至A執行。

這樣的狀態控制,保證了同一個key 在遷移之前總是在源節點執行。遷移後總是在目標節點執行,從而杜絕了雙寫的衝突。遷移過程中,新增加的key 會在目標節點執行,源節點不會新增key。使得遷移有界限,可以在某個確定的時刻結束。

單個key 的遷移過程可以通過原子化的migrate 命令完成。對於A/B的slave 節點,是通過主備複製,從而達到增刪資料。

當所有key 遷移完成後,Client 通過 cluster setslot 命令設定B的分片資訊,從而包含了遷入的slot。設定過程中會讓Epoch自增,並且是Cluster 中的最新值。然後通過相互感知,傳播到Cluster 中的其他節點。

failover

同Sentinel 一樣,Redis Cluster 也具備一套完整的故障發現、故障狀態一致性保證、主備切換機制。

1、failover的狀態變遷

  1. 故障發現:當某個master 宕機時,宕機時間如何被叢集其他節點感知。
  2. 故障確認:多個節點就某個master 是否宕機如何達成一致。
  3. slave選舉:叢集確認了某個master 宕機後,如何將它的slave 升級成新的master;如果有多個slave,如何選擇升級。
  4. 叢集結構變更:成功選舉成為master後,如何讓整個叢集知道,以更新Cluster 結構資訊。

2、故障發現

Redis Cluster 節點間通過Redis Cluster Bus 兩兩週期性的PING/PONG 互動。當某個節點宕機時,其他Node 發出的PING訊息沒有收到響應,並且超過一定時間(NODE_TIMEOUT)未收到,則認為該節點故障,將其置為PFAIL狀態(Possible Fail)。後續通過Gossip 發出的PING/PONG訊息中,這個節點的PFAIL 狀態會傳播到叢集的其他節點。

Redis Cluster 的節點兩兩通過TCP 保持Redis Cluster Bus連線,當對PING 無反饋時,可能是節點故障,也可能是TCP 連結斷開。如果是TCP 斷開導致的誤報,雖然誤報訊息會因為其他節點的正常連線被忽略,但是也可以通過一定的方式減少誤報。Redis Cluster 通過 預重試機制 排除此類誤報:當 NODE_TIMEOUT/2 過去了,但是還未收到響應,則重新連線重發PING 訊息,如果對端正常,則在很短的時間內就會有響應。

3、故障確認

對於網路分隔的情況,某個節點(B)並沒有故障,但是和A 無法連線,但是和C/D 等其他節點可以正常聯通。此時只會有A 將 B 標記為PFAIL 狀態,其他節點認為B 正常。此時A 和C/D 等其他節點資訊不一致,Redis Cluster 通過故障 確認協議 達成一致。

叢集中每個節點都是Gossip的接收者,A 也會接收到來自其他節點的Gossip 訊息,被告知B 是否處於PFAIL 狀態。當A收到來氣其他master 節點對於 B 的PFAIL 達到一定數量後,會將B的PFAIL 狀態升級為 FAIL 狀態。表示B 已經確認為故障態,後面會發起slave 選舉流程。

A節點內部的叢集資訊中,對於B的狀態從PFAIL 到 FAIL 的變遷,如下圖所示:

這裡寫圖片描述

4、slave選舉

上圖中,B是A的master,並且B 已經被叢集公認是FAIL 狀態了,那麼A 發起競選,期望成為新的master。

如果B 有多個slave (A/E/F)都認知到B 處於FAIL 狀態了,A/E/F 可能會同時發起競選。當B的slave 個數 >= 3 時,很有可能產生多輪競選失敗。為了減少衝突的出現,優先順序高的slave 更有可能發起競選,從而提升成功的可能性。這裡的優先順序是slave的資料最新的程度,資料越新的(最完整的)優先順序越高。

slave 通過向其他master傳送FAILVOER_AUTH_REQUEST 訊息發起競選,master 收到後回覆FAILOVER_AUTH_ACK 訊息告知是否同意。slave 傳送FAILOVER_AUTH_REQUEST 前會將currentEpoch 自增,並將最新的Epoch 帶入到FAILOVER_AUTH_REQUEST 訊息中,如果自己未投過票,則回覆同意,否則回覆拒絕。

5、結構變更通知

當slave 收到過半的master 同意時,會替代B 成為新的master。此時會以最新的Epoch 通過PONG 訊息廣播自己成為master,讓Cluster 的其他節點儘快的更新拓撲結構。

當B 恢復可用之後,它手續愛你仍然認為自己是master,但逐漸的通過Gossip 協議得知A 已經替代了自己,然後降級為A的slave。

可用性和效能

Redis Cluster 還提供了一些方法可以提升效能和可用性。

1、Redis Cluster的讀寫分離

對於讀寫分離的場景,應用對於某些讀請求允許捨棄一定的資料一致性,以換取更高的吞吐量。此時希望將讀請求交給slave處理,以分擔master的壓力。

通過分片對映關係,某個slot 一定對應著一個master節點。Client 通過moved 命令,也只會路由到各個master中。即使Client 將請求直接傳送到slave上,也會回覆moved 到master去處理。

為此,Redis Cluster 引入了readonly 命令。Client 向slave傳送該命令後,不再moved 到master處理,而是自己處理,這成為slave的readonly 模式。通過readwrite命令,可以將slave的readonly模式重置。

2、master單點保護

假如Cluster 的初始狀態如下所示:

這裡寫圖片描述

上圖中A、B兩個master 分別有自己的slave,假設A1 發生宕機,結構變為如下所示:

這裡寫圖片描述

此時A 成為了單點,一旦A 再次宕機,將造成不可用。此時Redis Cluster 會把B 的某個slave (如 B1 )進行副本遷移,變成A的slave。如下所示:

這裡寫圖片描述

這樣叢集中每個master 至少有一個slave,使得Cluster 具有高可用。叢集中只需要保持 2*master+1 個節點,就可以保持任一節點宕機時,故障轉移後繼續高可用。