天天看點

分布式Redis深度曆險-Cluster

本文為分布式Redis深度曆險系列的第三篇,主要内容為Redis的Cluster,也就是Redis叢集功能。

Redis叢集是Redis官方提供的分布式方案,整個叢集通過将所有資料分成16384個槽來進行資料共享。

一個叢集由多個Redis節點組成,不同的節點通過<code>CLUSTER MEET</code>指令進行連接配接:

<code>CLUSTER MEET &lt;ip&gt; &lt;port&gt;</code>

收到指令的節點會與指令中指定的目标節點進行握手,握手成功後目标節點會加入到叢集中,看個例子,圖檔來自于Redis的設計與實作:

一個叢集的所有資料被分為16384個槽,可以通過<code>CLUSTER ADDSLOTS</code>指令将槽指派給對應的節點。當所有的槽都有節點負責時,叢集處于上線狀态,否則處于下線狀态不對外提供服務。

clusterNode的位數組slots代表一個節點負責的槽資訊。

看個例子,下圖中1、3、5、8、9、10位的值為1,代表該節點負責槽1、3、5、8、9、10。

每個Redis Server上都有一個ClusterState的對象,代表了該Server所在叢集的資訊,其中字段slots記錄了叢集中所有節點負責的槽資訊。

可以通過redis-trib工具對槽重新配置設定,重配置設定的實作步驟如下:

通知目标節點準備好接收槽

通知源節點準備好發送槽

向源節點發送指令:<code>CLUSTER GETKEYSINSLOT &lt;slot&gt; &lt;count&gt;</code>從源節點擷取最多count個槽slot的key

對于步驟3的每個key,都向源節點發送一個<code>MIGRATE &lt;target_ip&gt; &lt;target_port&gt; &lt;key_name&gt; 0 &lt;timeout&gt; </code>指令,将被選中的鍵原子的從源節點遷移至目标節點。

重複步驟3、4。直到槽slot的所有鍵值對都被遷移到目标節點

将槽slot指派給目标節點的資訊發送到整個叢集。

在槽重配置設定的過程中,槽中的一部分資料儲存着源節點,另一部分儲存在目标節點。這時如果要用戶端向源節點發送一個指令,且相關資料在一個正在遷移槽中,源節點處理步驟如圖:

當用戶端收到一個ASK錯誤的時候,會根據傳回的資訊向目标節點重新發起一次請求。

ASK和MOVED的差別主要是ASK是一次性的,MOVED是永久性的,有點像Http協定中的301和302。

我們來看cluster下一次指令的請求過程,假設執行指令 <code>get testKey</code>

cluster client在運作前需要配置若幹個server節點的ip和port。我們稱這些節點為種子節點。

cluster的用戶端在執行指令時,會先通過計算得到key的槽資訊,計算規則為:<code>getCRC16(key) &amp; (16384 - 1)</code>,得到槽資訊後,會從一個緩存map中獲得槽對應的redis server資訊,如果能擷取到,則調到第4步

向種子節點發送<code>slots</code>指令以獲得整個叢集的槽分布資訊,然後跳轉到第2步重試指令

向負責該槽的server發起調用

server處理如圖:

用戶端如果收到MOVED錯誤,則根據對應的位址跳轉到第4步重新請求,

客戶段如果收到ASK錯誤,則根據對應的位址跳轉到第4步重新請求,并在請求前帶上ASKING辨別。

以上步驟大緻就是redis cluster下一次指令請求的過程,但忽略了一個細節,如果要查找的資料鎖所在的槽正在重配置設定怎麼辦?

叢集中每個Redis節點都會定期的向叢集中的其他節點發送PING消息,如果目标節點沒有在有效時間内回複PONG消息,則會被标記為疑似下線。同時将該資訊發送給其他節點。當一個叢集中有半數負責處理槽的主節點都将某個節點A标記為疑似下線後,那麼A會被标記為已下線,将A标記為已下線的節點會将該資訊發送給其他節點。

比如說有A,B,C,D,E 5個主節點。E有F、G兩個從節點。

當E節點發生異常後,其他節點發送給A的PING消息将不能得到正常回複。當過了最大逾時時間後,假設A,B先将E标記為疑似下線;之後C也會将E标記為疑似下線,這時C發現叢集中由3個節點(A、B、C)都将E标記為疑似下線,超過叢集複制槽的主節點個數的一半(&gt;2.5)則會将E标記為已下線,并向叢集廣播E下線的消息。

當F、G(E的從節點)收到E被标記已下線的消息後,會根據Raft算法選舉出一個新的主節點,新的主節點會将E複制的所有槽指派給自己,然後向叢集廣播消息,通知其他節點新的主節點資訊。

選舉新的主節點算法與選舉Sentinel頭節點的過程很像:

叢集的配置紀元是一個自增計數器,它的初始值為0.

當叢集裡的某個節點開始一次故障轉移操作時,叢集配置紀元的值會被增一。

對于每個配置紀元,叢集裡每個負責處理槽的主節點都有一次投票的機會,而第一個向主節點要求投票的從節點将獲得主節點的投票。

檔從節點發現自己正在複制的主節點進入已下線狀态時,從節點會想叢集廣播一條CLUSTER_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有接收到這條消息、并且具有投票權的主節點向這個從節點投票。

如果一個主節點具有投票權(它正在負責處理槽),并且這個主節點尚未投票給其他從節點,那麼主節點将向要求投票的從節點傳回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支援從節點成為新的主節點。

每個參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據自己收到了多少條這種消息來同濟自己獲得了多少主節點的支援。

如果叢集裡有N個具有投票權的主節點,那麼當一個從節點收集到大于等于N/2+1張支援票時,這個從節點就會當選為新的主節點。

因為在每一個配置紀元裡面,每個具有投票權的主節點隻能投一次票,是以如果有N個主節點進行投票,那麼具有大于等于N/2+1張支援票的從節點隻會有一個,這確定了新的主節點隻會有一個。

如果在一個配置紀元裡面沒有從節點能收集到足夠多的支援票,那麼叢集進入一個新的配置紀元,并再次進行選舉,知道選出新的主節點為止。

最後,聊聊redis叢集的其他兩種實作方案。

用戶端做路由,采用一緻性hash算法,将key映射到對應的redis節點上。

其優點是實作簡單,沒有引用其他中間件。

缺點也很明顯:是一種靜态分片方案,擴容性差。

Jedis中的ShardedJedis是該方案的實作。

該方案在client與redis之間引入一個代理層。client的所有操作都發送給代理層,由代理層實作路由轉發給不同的redis伺服器。

其優點是: 路由規則可自定義,擴容友善。

缺點是: 代理層有單點問題,多一層轉發的網絡開銷

繼續閱讀