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得到未知的其他節點
    • 循環上述過程,直到最終加入集羣

圖1 新節點加入

  • 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通知其他集羣節點

圖2 故障恢復

源碼

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:收到的節點不可達投票

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信息
本文轉自http://catkang.github.io/2016/05/08/redis-cluster-source.html,供學習記錄用,侵刪。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章