溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

分布式Redis深度歷險-Cluster

發(fā)布時間:2020-08-07 12:25:17 來源:網(wǎng)絡(luò) 閱讀:459 作者:Java筆記丶 欄目:編程語言

本文為分布式Redis深度歷險系列的第三篇,主要內(nèi)容為Redis的Cluster,也就是Redis集群功能。

Redis集群是Redis官方提供的分布式方案,整個集群通過將所有數(shù)據(jù)分成16384個槽來進行數(shù)據(jù)共享。

?

集群基礎(chǔ)實現(xiàn)

一個集群由多個Redis節(jié)點組成,不同的節(jié)點通過CLUSTER MEET命令進行連接:

CLUSTER MEET <ip> <port>

收到命令的節(jié)點會與命令中指定的目標(biāo)節(jié)點進行握手,握手成功后目標(biāo)節(jié)點會加入到集群中,看個例子,圖片來自于Redis的設(shè)計與實現(xiàn):

分布式Redis深度歷險-Cluster

分布式Redis深度歷險-Cluster

分布式Redis深度歷險-Cluster

分布式Redis深度歷險-Cluster

分布式Redis深度歷險-Cluster

?

槽分配

一個集群的所有數(shù)據(jù)被分為16384個槽,可以通過CLUSTER ADDSLOTS命令將槽指派給對應(yīng)的節(jié)點。當(dāng)所有的槽都有節(jié)點負責(zé)時,集群處于上線狀態(tài),否則處于下線狀態(tài)不對外提供服務(wù)。

clusterNode的位數(shù)組slots代表一個節(jié)點負責(zé)的槽信息。

struct?clusterNode?{


????unsigned?char?slots[16384/8];?/*?slots?handled?by?this?node?*/

????int?numslots;???/*?Number?of?slots?handled?by?this?node?*/

????...
}

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

每個Redis Server上都有一個ClusterState的對象,代表了該Server所在集群的信息,其中字段slots記錄了集群中所有節(jié)點負責(zé)的槽信息。

typedef?struct?clusterState?{

????//?負責(zé)處理各個槽的節(jié)點
????//?例如?slots[i]?=?clusterNode_A?表示槽?i?由節(jié)點?A?處理
????//?slots[i]?=?null?代表該槽目前沒有節(jié)點負責(zé)
????clusterNode?*slots[REDIS_CLUSTER_SLOTS];

}

?

槽重分配

可以通過redis-trib工具對槽重新分配,重分配的實現(xiàn)步驟如下:

  1. 通知目標(biāo)節(jié)點準備好接收槽

  2. 通知源節(jié)點準備好發(fā)送槽

  3. 向源節(jié)點發(fā)送命令:CLUSTER GETKEYSINSLOT <slot> <count>從源節(jié)點獲取最多count個槽slot的key

  4. 對于步驟3的每個key,都向源節(jié)點發(fā)送一個MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>?命令,將被選中的鍵原子的從源節(jié)點遷移至目標(biāo)節(jié)點。

  5. 重復(fù)步驟3、4。直到槽slot的所有鍵值對都被遷移到目標(biāo)節(jié)點

  6. 將槽slot指派給目標(biāo)節(jié)點的信息發(fā)送到整個集群。

在槽重分配的過程中,槽中的一部分數(shù)據(jù)保存著源節(jié)點,另一部分保存在目標(biāo)節(jié)點。這時如果要客戶端向源節(jié)點發(fā)送一個命令,且相關(guān)數(shù)據(jù)在一個正在遷移槽中,源節(jié)點處理步驟如圖:
分布式Redis深度歷險-Cluster

當(dāng)客戶端收到一個ASK錯誤的時候,會根據(jù)返回的信息向目標(biāo)節(jié)點重新發(fā)起一次請求。

ASK和MOVED的區(qū)別主要是ASK是一次性的,MOVED是永久性的,有點像Http協(xié)議中的301和302。

?

一次命令執(zhí)行過程

我們來看cluster下一次命令的請求過程,假設(shè)執(zhí)行命令?get testKey

  1. cluster client在運行前需要配置若干個server節(jié)點的ip和port。我們稱這些節(jié)點為種子節(jié)點。

  2. cluster的客戶端在執(zhí)行命令時,會先通過計算得到key的槽信息,計算規(guī)則為:getCRC16(key) & (16384 - 1),得到槽信息后,會從一個緩存map中獲得槽對應(yīng)的redis server信息,如果能獲取到,則調(diào)到第4步

  3. 向種子節(jié)點發(fā)送slots命令以獲得整個集群的槽分布信息,然后跳轉(zhuǎn)到第2步重試命令

  4. 向負責(zé)該槽的server發(fā)起調(diào)用
    server處理如圖:
    分布式Redis深度歷險-Cluster

  5. 客戶端如果收到MOVED錯誤,則根據(jù)對應(yīng)的地址跳轉(zhuǎn)到第4步重新請求,

  6. 客戶段如果收到ASK錯誤,則根據(jù)對應(yīng)的地址跳轉(zhuǎn)到第4步重新請求,并在請求前帶上ASKING標(biāo)識。

以上步驟大致就是redis cluster下一次命令請求的過程,但忽略了一個細節(jié),如果要查找的數(shù)據(jù)鎖所在的槽正在重分配怎么辦?

?

Redis故障轉(zhuǎn)移

疑似下線與已下線

集群中每個Redis節(jié)點都會定期的向集群中的其他節(jié)點發(fā)送PING消息,如果目標(biāo)節(jié)點沒有在有效時間內(nèi)回復(fù)PONG消息,則會被標(biāo)記為疑似下線。同時將該信息發(fā)送給其他節(jié)點。當(dāng)一個集群中有半數(shù)負責(zé)處理槽的主節(jié)點都將某個節(jié)點A標(biāo)記為疑似下線后,那么A會被標(biāo)記為已下線,將A標(biāo)記為已下線的節(jié)點會將該信息發(fā)送給其他節(jié)點。

比如說有A,B,C,D,E 5個主節(jié)點。E有F、G兩個從節(jié)點。
當(dāng)E節(jié)點發(fā)生異常后,其他節(jié)點發(fā)送給A的PING消息將不能得到正?;貜?fù)。當(dāng)過了最大超時時間后,假設(shè)A,B先將E標(biāo)記為疑似下線;之后C也會將E標(biāo)記為疑似下線,這時C發(fā)現(xiàn)集群中由3個節(jié)點(A、B、C)都將E標(biāo)記為疑似下線,超過集群復(fù)制槽的主節(jié)點個數(shù)的一半(>2.5)則會將E標(biāo)記為已下線,并向集群廣播E下線的消息。

?

選取新的主節(jié)點

當(dāng)F、G(E的從節(jié)點)收到E被標(biāo)記已下線的消息后,會根據(jù)Raft算法選舉出一個新的主節(jié)點,新的主節(jié)點會將E復(fù)制的所有槽指派給自己,然后向集群廣播消息,通知其他節(jié)點新的主節(jié)點信息。

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

  1. 集群的配置紀元是一個自增計數(shù)器,它的初始值為0.

  2. 當(dāng)集群里的某個節(jié)點開始一次故障轉(zhuǎn)移操作時,集群配置紀元的值會被增一。

  3. 對于每個配置紀元,集群里每個負責(zé)處理槽的主節(jié)點都有一次投票的機會,而第一個向主節(jié)點要求投票的從節(jié)點將獲得主節(jié)點的投票。

  4. 檔從節(jié)點發(fā)現(xiàn)自己正在復(fù)制的主節(jié)點進入已下線狀態(tài)時,從節(jié)點會想集群廣播一條CLUSTER_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有接收到這條消息、并且具有投票權(quán)的主節(jié)點向這個從節(jié)點投票。

  5. 如果一個主節(jié)點具有投票權(quán)(它正在負責(zé)處理槽),并且這個主節(jié)點尚未投票給其他從節(jié)點,那么主節(jié)點將向要求投票的從節(jié)點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節(jié)點支持從節(jié)點成為新的主節(jié)點。

  6. 每個參與選舉的從節(jié)點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據(jù)自己收到了多少條這種消息來同濟自己獲得了多少主節(jié)點的支持。

  7. 如果集群里有N個具有投票權(quán)的主節(jié)點,那么當(dāng)一個從節(jié)點收集到大于等于N/2+1張支持票時,這個從節(jié)點就會當(dāng)選為新的主節(jié)點。

  8. 因為在每一個配置紀元里面,每個具有投票權(quán)的主節(jié)點只能投一次票,所以如果有N個主節(jié)點進行投票,那么具有大于等于N/2+1張支持票的從節(jié)點只會有一個,這確保了新的主節(jié)點只會有一個。

  9. 如果在一個配置紀元里面沒有從節(jié)點能收集到足夠多的支持票,那么集群進入一個新的配置紀元,并再次進行選舉,知道選出新的主節(jié)點為止。

?

Redis常用分布式實現(xiàn)方案

最后,聊聊redis集群的其他兩種實現(xiàn)方案。

client做分片

客戶端做路由,采用一致性hash算法,將key映射到對應(yīng)的redis節(jié)點上。
其優(yōu)點是實現(xiàn)簡單,沒有引用其他中間件。
缺點也很明顯:是一種靜態(tài)分片方案,擴容性差。

Jedis中的ShardedJedis是該方案的實現(xiàn)。

proxy做分片

該方案在client與redis之間引入一個代理層。client的所有操作都發(fā)送給代理層,由代理層實現(xiàn)路由轉(zhuǎn)發(fā)給不同的redis服務(wù)器。

分布式Redis深度歷險-Cluster

其優(yōu)點是: 路由規(guī)則可自定義,擴容方便。
缺點是: 代理層有單點問題,多一層轉(zhuǎn)發(fā)的網(wǎng)絡(luò)開銷

?


向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI