Redis Cluster 實現細節
Redis Cluster 實現
本文將從設計思路,功能實現,原始碼幾個方面介紹Redis Cluster。假設讀者已經瞭解Redis Cluster的使用方式。
簡介
Redis Cluster作為Redis的分散式實現,主要做了兩個方面的事情:
1,資料分片
- Redis Cluster將資料按key雜湊到16384個slot上
- Cluster中的不同節點負責一部分slot
2,故障恢復
- Cluster中直接提供服務的節點為Master
- 每個Master可以有一個或多個Slave
- 當Master不能提供服務時,Slave會自動Failover
設計思路
效能為第一目標
- 每一次資料處理都是由負責當前slot的Master直接處理的,沒有額外的網路開銷
提高可用性
- 水平擴充套件能力 :由於slot的存在,增加機器節點時只需要將之前由其他節點處理的一部分slot重新分配給新增節點。slot可以看做機器節點和使用者資料之間的一個抽象層。
- 故障恢復:Slave會在需要的時候自動提升為Master
損失一致性
- Master與Slave之之間非同步複製,即Master先向使用者返回結果後再非同步將資料同步給Slave,這就導致Master宕機後一部分已經返回使用者的資料在新Master上不存在
- 網路分割槽時,由於開始Failover前的超時時間,會有一部分資料繼續寫到馬上要失效的Master上
功能實現
1,資料分片
我們已經知道資料會按照key雜湊到不同的slot,而每個節點僅負責一部分的slot,客戶端根據slot將請求交給不同的節點。將slots劃分給不同節點的過程稱為資料分片,對應的還可以進行分片的重新分配。這部分功能依賴外部呼叫命令:
分片
- 對每個叢集執行
CLUSTER ADDSLOTS slot [slot ...]
- RedisCluster將命令指定的slots作為自己負責的部分
再分配
再分配要做的是將一些slots從當前節點(source)遷移到其他節點(target)
- 對target執行
CLUSTER SETSLOT slot IMPORTING [node-id]
,target節點將對應slots記為importing狀態; - 對source執行
CLUSTER SETSLOT MIGRATING[node-id]
,source節點將對應slots記為migrating狀態,與importing狀態一同在之後的請求重定向中使用 - 獲取所有要遷移slot對應的keys,
CLUSTER GETKEYSINSLOT slot count
- 對source 執行
MIGRATE host port key db timeout REPLACE [KEYS key [key ...]]
- MIGRATE命令會將所有的指定的key通過
RESTORE key ttl serialized-value REPLACE
遷移給target - 對所有節點執行
CLUSTER SETSLOT slot NODE [node-id]
,申明target對這些slots的負責,並退出importing或migrating
2,請求重定向
由於每個節點只負責部分slot,以及slot可能從一個節點遷移到另一節點,造成客戶端有可能會向錯誤的節點發起請求。因此需要有一種機制來對其進行發現和修正,這就是請求重定向。有兩種不同的重定向場景:
1),MOVE
- ‘我’並不負責‘你’要的key,告訴’你‘正確的吧。
- 返回
CLUSTER_REDIR_MOVED
錯誤,和正確的節點。 - 客戶端向該節點重新發起請求,注意這次依然又發生重定向的可能。
2),ASK
- ‘我’負責請求的key,但不巧的這個key當前在migraging狀態,且‘我’這裡已經取不到了。告訴‘你’importing他的‘傢伙’吧,去碰碰運氣。
- 返回
CLUSTER_REDIR_ASK
,和importing該key的節點。 - 客戶端向新節點發送
ASKING
,之後再次發起請求 - 新節點對傳送過
ASKING
,且key已經migrate過來的請求進行響應
3),區別
區分這兩種重定向的場景是非常有必要的:
- MOVE,申明的是slot所有權的轉移,收到的客戶端需要更新其key-node對映關係
- ASK,申明的是一種臨時的狀態,所有權還並沒有轉移,客戶端並不更新其對映關係。前面的加的ASKING命令也是申明其理解當前的這種臨時狀態
3,狀態檢測及維護
Cluster中的每個節點都維護一份在自己看來當前整個叢集的狀態,主要包括:
- 當前叢集狀態
- 叢集中各節點所負責的slots資訊,及其migrate狀態
- 叢集中各節點的master-slave狀態
- 叢集中各節點的存活狀態及不可達投票
當叢集狀態變化時,如新節點加入、slot遷移、節點宕機、slave提升為新Master,我們希望這些變化儘快的被發現,傳播到整個叢集的所有節點並達成一致。節點之間相互的心跳(PING,PONG,MEET)及其攜帶的資料是叢集狀態傳播最主要的途徑。
心跳時機:
Redis節點會記錄其向每一個節點上一次發出ping和收到pong的時間,心跳傳送時機與這兩個值有關。通過下面的方式既能保證及時更新叢集狀態,又不至於使心跳數過多:
- 每次Cron向所有未建立連結的節點發送ping或meet
- 每1秒從所有已知節點中隨機選取5個,向其中上次收到pong最久遠的一個傳送ping
- 每次Cron向收到pong超過timeout/2的節點發送ping
- 收到ping或meet,立即回覆pong
心跳資料
- Header,傳送者自己的資訊
- 所負責slots的資訊
- 主從資訊
- ip port資訊
- 狀態資訊
- Gossip,傳送者所瞭解的部分其他節點的資訊
- ping_sent, pong_received
- ip, port資訊
- 狀態資訊,比如傳送者認為該節點已經不可達,會在狀態資訊中標記其為PFAIL或FAIL
心跳處理
- 1,新節點加入
- 傳送meet包加入叢集
- 從pong包中的gossip得到未知的其他節點
- 迴圈上述過程,直到最終加入叢集
- 2,Slots資訊
- 判斷髮送者宣告的slots資訊,跟本地記錄的是否有不同
- 如果不同,且傳送者epoch較大,更新本地記錄
- 如果不同,且傳送者epoch小,傳送Update資訊通知傳送者
- 3,Master slave資訊
- 發現傳送者的master、slave資訊變化,更新本地狀態
- 4,節點Fail探測
- 超過超時時間仍然沒有收到pong包的節點會被當前節點標記為PFAIL
- PFAIL標記會隨著gossip傳播
- 每次收到心跳包會檢測其中對其他節點的PFAIL標記,當做對該節點FAIL的投票維護在本機
- 對某個節點的PFAIL標記達到大多數時,將其變為FAIL標記並廣播FAIL訊息
注:Gossip的存在使得叢集狀態的改變可以更快的達到整個叢集。每個心跳包中會包含多個Gossip包,那麼多少個才是合適的呢,redis的選擇是N/10,其中N是節點數,這樣可以保證在PFAIL投票的過期時間內,節點可以收到80%機器關於失敗節點的gossip,從而使其順利進入FAIL狀態。
廣播
當需要釋出一些非常重要需要立即送達的資訊時,上述心跳加Gossip的方式就顯得捉襟見肘了,這時就需要向所有叢集內機器的廣播資訊,使用廣播發的場景:
- 節點的Fail資訊:當發現某一節點不可達時,探測節點會將其標記為PFAIL狀態,並通過心跳傳播出去。當某一節點發現這個節點的PFAIL超過半數時修改其為FAIL併發起廣播。
- Failover Request資訊:slave嘗試發起FailOver時廣播其要求投票的資訊
- 新Master資訊:Failover成功的節點向整個叢集廣播自己的資訊
4,故障恢復(Failover)
當slave發現自己的master變為FAIL狀態時,便嘗試進行Failover,以期成為新的master。由於掛掉的master可能會有多個slave。Failover的過程需要經過類Raft協議的過程在整個叢集內達到一致, 其過程如下:
- slave發現自己的master變為FAIL
- 將自己記錄的叢集currentEpoch加1,並廣播Failover Request資訊
- 其他節點收到該資訊,只有master響應,判斷請求者的合法性,併發送FAILOVER_AUTH_ACK,對每一個epoch只發送一次ack
- 嘗試failover的slave收集FAILOVER_AUTH_ACK
- 超過半數後變成新Master
- 廣播Pong通知其他叢集節點
原始碼
1,資料結構
clusterState, 從當前節點的視角來看的叢集狀態,每個節點維護一份
- myself:指標指向自己的clusterNode
- currentEpoch:當前節點見過的最大epoch,可能在心跳包的處理中更新
- nodes:當前節點感知到的所有節點,為clusterNode指標陣列
- slots:slot與clusterNode指標對映關係
- migrating_slots_to, importing_slots_from:記錄slots的遷移資訊
- failover_auth_time, failover_auth_count, failover_auth_sent, failover_auth_rank, failover_auth_epoch:Failover相關
clusterNode,代表叢集中的一個節點
- slots:點陣圖,由當前clusterNode負責的slot為1
- salve, slaveof:主從關係資訊
- ping_sent, pong_received:心跳包收發時間
- clusterLink *link:Node間的聯接
- list *fail_reports:收到的節點不可達投票
clusterLink,負責處理網路上的一條連結來的內容
2,Redis啟動過程中與Cluster相關內容
- 初始化或從檔案中恢復cluster結構
- 註冊叢集間通訊訊息的處理函式:clusterProcessPacket
- 增加Cluster相關的Cron函式:clusterCron
3,客戶端請求重定向
- redis處理客戶端命令的函式processCommand增加cluster的重定向內容
- 事務或多key中若落在不同slots,直接返回CLUSTER_REDIR_CROSS_SLOT
- 如果當前存在於migration狀態,且有key不再當前節點,返回CLUSTER_REDIR_ASK
- 如果當前是import狀態且客戶端在ASKING狀態,則返回可以處理,或者CLUSTER_REDIR_UNSTABLE
- 如果不是myself,則返回CLUSTER_REDIR_MOVED
4,定時任務 clusterCron
- 對handshake節點建立Link,傳送Ping或Meet
- 選擇合適的clusterNode傳送Ping
- 如果是從檢視是否需要做Failover
- 統計並決定是否進行slave的遷移,來平衡不同master的slave數
- 判斷所有pfail報告數是否過半數
5,叢集訊息處理 clusterProcessPacket
- 根據收到的訊息更新自己的epoch和slave的offset資訊
- 處理MEET訊息,使加入叢集
- 從goosip中發現未知節點,發起handshake
- 對PING,MEET回覆PONG
- 根據收到的心跳資訊更新自己clusterState中的master-slave,slots資訊
- 對FAILOVER_AUTH_REQUEST訊息,檢查並投票
- 處理FAIL,FAILOVER_AUTH_ACK,UPDATE資訊