本文將從設(shè)計思路,功能實(shí)現(xiàn),源碼幾個方面介紹Redis Cluster。假設(shè)讀者已經(jīng)了解Redis Cluster的使用方式。
簡介
Redis Cluster作為Redis的分布式實(shí)現(xiàn),主要做了兩個方面的事情:
1,數(shù)據(jù)分片
- Redis Cluster將數(shù)據(jù)按key哈希到16384個slot上
- Cluster中的不同節(jié)點(diǎn)負(fù)責(zé)一部分slot
2,故障恢復(fù)
- Cluster中直接提供服務(wù)的節(jié)點(diǎn)為Master
- 每個Master可以有一個或多個Slave
- 當(dāng)Master不能提供服務(wù)時,Slave會自動Failover
設(shè)計思路
性能為第一目標(biāo)
- 每一次數(shù)據(jù)處理都是由負(fù)責(zé)當(dāng)前slot的Master直接處理的,沒有額外的網(wǎng)絡(luò)開銷
提高可用性
- 水平擴(kuò)展能力 :由于slot的存在,增加機(jī)器節(jié)點(diǎn)時只需要將之前由其他節(jié)點(diǎn)處理的一部分slot重新分配給新增節(jié)點(diǎn)。slot可以看做機(jī)器節(jié)點(diǎn)和用戶數(shù)據(jù)之間的一個抽象層。
- 故障恢復(fù):Slave會在需要的時候自動提升為Master
損失一致性
- Master與Slave之之間異步復(fù)制,即Master先向用戶返回結(jié)果后再異步將數(shù)據(jù)同步給Slave,這就導(dǎo)致Master宕機(jī)后一部分已經(jīng)返回用戶的數(shù)據(jù)在新Master上不存在
- 網(wǎng)絡(luò)分區(qū)時,由于開始Failover前的超時時間,會有一部分?jǐn)?shù)據(jù)繼續(xù)寫到馬上要失效的Master上
功能實(shí)現(xiàn)
1,數(shù)據(jù)分片
我們已經(jīng)知道數(shù)據(jù)會按照key哈希到不同的slot,而每個節(jié)點(diǎn)僅負(fù)責(zé)一部分的slot,客戶端根據(jù)slot將請求交給不同的節(jié)點(diǎn)。將slots劃分給不同節(jié)點(diǎn)的過程稱為數(shù)據(jù)分片,對應(yīng)的還可以進(jìn)行分片的重新分配。這部分功能依賴外部調(diào)用命令:
分片
- 對每個集群執(zhí)行
CLUSTER ADDSLOTS slot [slot ...] - RedisCluster將命令指定的slots作為自己負(fù)責(zé)的部分
再分配
再分配要做的是將一些slots從當(dāng)前節(jié)點(diǎn)(source)遷移到其他節(jié)點(diǎn)(target)
- 對target執(zhí)行
CLUSTER SETSLOT slot IMPORTING [node-id],target節(jié)點(diǎn)將對應(yīng)slots記為importing狀態(tài); - 對source執(zhí)行
CLUSTER SETSLOT MIGRATING[node-id],source節(jié)點(diǎn)將對應(yīng)slots記為migrating狀態(tài),與importing狀態(tài)一同在之后的請求重定向中使用 - 獲取所有要遷移slot對應(yīng)的keys,
CLUSTER GETKEYSINSLOT slot count - 對source 執(zhí)行
MIGRATE host port key db timeout REPLACE [KEYS key [key ...]] - MIGRATE命令會將所有的指定的key通過
RESTORE key ttl serialized-value REPLACE遷移給target - 對所有節(jié)點(diǎn)執(zhí)行
CLUSTER SETSLOT slot NODE [node-id],申明target對這些slots的負(fù)責(zé),并退出importing或migrating
2,請求重定向
由于每個節(jié)點(diǎn)只負(fù)責(zé)部分slot,以及slot可能從一個節(jié)點(diǎn)遷移到另一節(jié)點(diǎn),造成客戶端有可能會向錯誤的節(jié)點(diǎn)發(fā)起請求。因此需要有一種機(jī)制來對其進(jìn)行發(fā)現(xiàn)和修正,這就是請求重定向。有兩種不同的重定向場景:
1),MOVE
- ‘我’并不負(fù)責(zé)‘你’要的key,告訴’你‘正確的吧。
- 返回
CLUSTER_REDIR_MOVED錯誤,和正確的節(jié)點(diǎn)。 - 客戶端向該節(jié)點(diǎn)重新發(fā)起請求,注意這次依然又發(fā)生重定向的可能。
2),ASK
- ‘我’負(fù)責(zé)請求的key,但不巧的這個key當(dāng)前在migraging狀態(tài),且‘我’這里已經(jīng)取不到了。告訴‘你’importing他的‘家伙’吧,去碰碰運(yùn)氣。
- 返回
CLUSTER_REDIR_ASK,和importing該key的節(jié)點(diǎn)。 - 客戶端向新節(jié)點(diǎn)發(fā)送
ASKING,之后再次發(fā)起請求 - 新節(jié)點(diǎn)對發(fā)送過
ASKING,且key已經(jīng)migrate過來的請求進(jìn)行響應(yīng)
3),區(qū)別
區(qū)分這兩種重定向的場景是非常有必要的:
- MOVE,申明的是slot所有權(quán)的轉(zhuǎn)移,收到的客戶端需要更新其key-node映射關(guān)系
- ASK,申明的是一種臨時的狀態(tài),所有權(quán)還并沒有轉(zhuǎn)移,客戶端并不更新其映射關(guān)系。前面的加的ASKING命令也是申明其理解當(dāng)前的這種臨時狀態(tài)
3,狀態(tài)檢測及維護(hù)
Cluster中的每個節(jié)點(diǎn)都維護(hù)一份在自己看來當(dāng)前整個集群的狀態(tài),主要包括:
- 當(dāng)前集群狀態(tài)
- 集群中各節(jié)點(diǎn)所負(fù)責(zé)的slots信息,及其migrate狀態(tài)
- 集群中各節(jié)點(diǎn)的master-slave狀態(tài)
- 集群中各節(jié)點(diǎn)的存活狀態(tài)及不可達(dá)投票
當(dāng)集群狀態(tài)變化時,如新節(jié)點(diǎn)加入、slot遷移、節(jié)點(diǎn)宕機(jī)、slave提升為新Master,我們希望這些變化盡快的被發(fā)現(xiàn),傳播到整個集群的所有節(jié)點(diǎn)并達(dá)成一致。節(jié)點(diǎn)之間相互的心跳(PING,PONG,MEET)及其攜帶的數(shù)據(jù)是集群狀態(tài)傳播最主要的途徑。
心跳時機(jī):
Redis節(jié)點(diǎn)會記錄其向每一個節(jié)點(diǎn)上一次發(fā)出ping和收到pong的時間,心跳發(fā)送時機(jī)與這兩個值有關(guān)。通過下面的方式既能保證及時更新集群狀態(tài),又不至于使心跳數(shù)過多:
- 每次Cron向所有未建立鏈接的節(jié)點(diǎn)發(fā)送ping或meet
- 每1秒從所有已知節(jié)點(diǎn)中隨機(jī)選取5個,向其中上次收到pong最久遠(yuǎn)的一個發(fā)送ping
- 每次Cron向收到pong超過timeout/2的節(jié)點(diǎn)發(fā)送ping
- 收到ping或meet,立即回復(fù)pong
心跳數(shù)據(jù)
- Header,發(fā)送者自己的信息
- 所負(fù)責(zé)slots的信息
- 主從信息
- ip port信息
- 狀態(tài)信息
- Gossip,發(fā)送者所了解的部分其他節(jié)點(diǎn)的信息
- ping_sent, pong_received
- ip, port信息
- 狀態(tài)信息,比如發(fā)送者認(rèn)為該節(jié)點(diǎn)已經(jīng)不可達(dá),會在狀態(tài)信息中標(biāo)記其為PFAIL或FAIL
心跳處理
- 1,新節(jié)點(diǎn)加入
- 發(fā)送meet包加入集群
- 從pong包中的gossip得到未知的其他節(jié)點(diǎn)
- 循環(huán)上述過程,直到最終加入集群

- 2,Slots信息
- 判斷發(fā)送者聲明的slots信息,跟本地記錄的是否有不同
- 如果不同,且發(fā)送者epoch較大,更新本地記錄
- 如果不同,且發(fā)送者epoch小,發(fā)送Update信息通知發(fā)送者
- 3,Master slave信息
- 發(fā)現(xiàn)發(fā)送者的master、slave信息變化,更新本地狀態(tài)
- 4,節(jié)點(diǎn)Fail探測
- 超過超時時間仍然沒有收到pong包的節(jié)點(diǎn)會被當(dāng)前節(jié)點(diǎn)標(biāo)記為PFAIL
- PFAIL標(biāo)記會隨著gossip傳播
- 每次收到心跳包會檢測其中對其他節(jié)點(diǎn)的PFAIL標(biāo)記,當(dāng)做對該節(jié)點(diǎn)FAIL的投票維護(hù)在本機(jī)
- 對某個節(jié)點(diǎn)的PFAIL標(biāo)記達(dá)到大多數(shù)時,將其變?yōu)镕AIL標(biāo)記并廣播FAIL消息
注:Gossip的存在使得集群狀態(tài)的改變可以更快的達(dá)到整個集群。每個心跳包中會包含多個Gossip包,那么多少個才是合適的呢,redis的選擇是N/10,其中N是節(jié)點(diǎn)數(shù),這樣可以保證在PFAIL投票的過期時間內(nèi),節(jié)點(diǎn)可以收到80%機(jī)器關(guān)于失敗節(jié)點(diǎn)的gossip,從而使其順利進(jìn)入FAIL狀態(tài)。
廣播
當(dāng)需要發(fā)布一些非常重要需要立即送達(dá)的信息時,上述心跳加Gossip的方式就顯得捉襟見肘了,這時就需要向所有集群內(nèi)機(jī)器的廣播信息,使用廣播發(fā)的場景:
- 節(jié)點(diǎn)的Fail信息:當(dāng)發(fā)現(xiàn)某一節(jié)點(diǎn)不可達(dá)時,探測節(jié)點(diǎn)會將其標(biāo)記為PFAIL狀態(tài),并通過心跳傳播出去。當(dāng)某一節(jié)點(diǎn)發(fā)現(xiàn)這個節(jié)點(diǎn)的PFAIL超過半數(shù)時修改其為FAIL并發(fā)起廣播。
- Failover Request信息:slave嘗試發(fā)起FailOver時廣播其要求投票的信息
- 新Master信息:Failover成功的節(jié)點(diǎn)向整個集群廣播自己的信息
4,故障恢復(fù)(Failover)
當(dāng)slave發(fā)現(xiàn)自己的master變?yōu)镕AIL狀態(tài)時,便嘗試進(jìn)行Failover,以期成為新的master。由于掛掉的master可能會有多個slave。Failover的過程需要經(jīng)過類Raft協(xié)議的過程在整個集群內(nèi)達(dá)到一致, 其過程如下:
- slave發(fā)現(xiàn)自己的master變?yōu)镕AIL
- 將自己記錄的集群currentEpoch加1,并廣播Failover Request信息
- 其他節(jié)點(diǎn)收到該信息,只有master響應(yīng),判斷請求者的合法性,并發(fā)送FAILOVER_AUTH_ACK,對每一個epoch只發(fā)送一次ack
- 嘗試failover的slave收集FAILOVER_AUTH_ACK
- 超過半數(shù)后變成新Master
- 廣播Pong通知其他集群節(jié)點(diǎn)

源碼
1,數(shù)據(jù)結(jié)構(gòu)
clusterState, 從當(dāng)前節(jié)點(diǎn)的視角來看的集群狀態(tài),每個節(jié)點(diǎn)維護(hù)一份
- myself:指針指向自己的clusterNode
- currentEpoch:當(dāng)前節(jié)點(diǎn)見過的最大epoch,可能在心跳包的處理中更新
- nodes:當(dāng)前節(jié)點(diǎn)感知到的所有節(jié)點(diǎn),為clusterNode指針數(shù)組
- slots:slot與clusterNode指針映射關(guān)系
- migrating_slots_to, importing_slots_from:記錄slots的遷移信息
- failover_auth_time, failover_auth_count, failover_auth_sent, failover_auth_rank, failover_auth_epoch:Failover相關(guān)
clusterNode,代表集群中的一個節(jié)點(diǎn)
- slots:位圖,由當(dāng)前clusterNode負(fù)責(zé)的slot為1
- salve, slaveof:主從關(guān)系信息
- ping_sent, pong_received:心跳包收發(fā)時間
- clusterLink *link:Node間的聯(lián)接
- list *fail_reports:收到的節(jié)點(diǎn)不可達(dá)投票
clusterLink,負(fù)責(zé)處理網(wǎng)絡(luò)上的一條鏈接來的內(nèi)容
2,Redis啟動過程中與Cluster相關(guān)內(nèi)容
- 初始化或從文件中恢復(fù)cluster結(jié)構(gòu)
- 注冊集群間通信消息的處理函數(shù):clusterProcessPacket
- 增加Cluster相關(guān)的Cron函數(shù):clusterCron
3,客戶端請求重定向
- redis處理客戶端命令的函數(shù)processCommand增加cluster的重定向內(nèi)容
- 事務(wù)或多key中若落在不同slots,直接返回CLUSTER_REDIR_CROSS_SLOT
- 如果當(dāng)前存在于migration狀態(tài),且有key不再當(dāng)前節(jié)點(diǎn),返回CLUSTER_REDIR_ASK
- 如果當(dāng)前是import狀態(tài)且客戶端在ASKING狀態(tài),則返回可以處理,或者CLUSTER_REDIR_UNSTABLE
- 如果不是myself,則返回CLUSTER_REDIR_MOVED
4,定時任務(wù) clusterCron
- 對handshake節(jié)點(diǎn)建立Link,發(fā)送Ping或Meet
- 選擇合適的clusterNode發(fā)送Ping
- 如果是從查看是否需要做Failover
- 統(tǒng)計并決定是否進(jìn)行slave的遷移,來平衡不同master的slave數(shù)
- 判斷所有pfail報告數(shù)是否過半數(shù)
5,集群消息處理 clusterProcessPacket
- 根據(jù)收到的消息更新自己的epoch和slave的offset信息
- 處理MEET消息,使加入集群
- 從goosip中發(fā)現(xiàn)未知節(jié)點(diǎn),發(fā)起handshake
- 對PING,MEET回復(fù)PONG
- 根據(jù)收到的心跳信息更新自己clusterState中的master-slave,slots信息
- 對FAILOVER_AUTH_REQUEST消息,檢查并投票
- 處理FAIL,F(xiàn)AILOVER_AUTH_ACK,UPDATE信息
參考
- Tutorial:Redis Cluster Tutorial
- Specification: Redis Cluster Specification
- Source:Github
- Life in a Redis Cluster: Meet and Gossip with your neighbors