新聞中心
Redis是非常流行的緩存。在Redis升級(jí)到3.0版本后,升級(jí)到集群版本,被稱(chēng)之為Redis Cluster。在集群版本中,會(huì)將數(shù)據(jù)分成多份,被保存到多個(gè)server中,從而保證集群的水平擴(kuò)展能力,加之每份數(shù)據(jù)保存多個(gè)副本,從而保證可用性,并且集群版本保證一定程度的Write Safety。本文詳細(xì)介紹Redis Cluster的實(shí)現(xiàn)細(xì)節(jié),從而分析Redis Cluster的Write Safety的保證程度。

創(chuàng)新互聯(lián)主營(yíng)宜豐網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,成都app軟件開(kāi)發(fā)公司,宜豐h5成都微信小程序搭建,宜豐網(wǎng)站營(yíng)銷(xiāo)推廣歡迎宜豐等地區(qū)企業(yè)咨詢(xún)
一、接口和架構(gòu)
1、接口
Redis Cluster的接口基本向前兼容,仍然是key-value類(lèi)型。
2、架構(gòu)
Redis Cluster包含server和client兩個(gè)組件。一個(gè)Redis Cluster可以包含多個(gè)server,可以包含多個(gè)客戶(hù)端。每個(gè)客戶(hù)端可以連接任意的server,讀取寫(xiě)入數(shù)據(jù)。保存在Redis Cluster中的數(shù)據(jù)會(huì)被分成多份,分散地保存在多個(gè)server中,并且每一份數(shù)據(jù)也會(huì)保存多個(gè)副本。
二、實(shí)現(xiàn)
1、節(jié)點(diǎn)
在Redis Cluster中,數(shù)據(jù)會(huì)被保存到多個(gè)Redis server中,每個(gè)Redis server都是一個(gè)獨(dú)立的進(jìn)程,具有獨(dú)立的IP和Port,也被稱(chēng)之為一個(gè)實(shí)例,或者叫做節(jié)點(diǎn)(Node)。Client通過(guò)這個(gè)IP和Port連接到這個(gè)Node。
每個(gè)節(jié)點(diǎn)都有個(gè)node id,node id是一個(gè)全局唯一的標(biāo)識(shí),它是在集群創(chuàng)建時(shí)隨機(jī)生成的。一個(gè)節(jié)點(diǎn)的id始終都不會(huì)變化的,但是node的IP和port是可以變化的。
2、Node table
一個(gè)Redis Cluster包含哪些節(jié)點(diǎn),也就是這個(gè)集群包含哪些節(jié)點(diǎn)的id,也就是集群成員關(guān)系,這個(gè)信息被保存在一個(gè)表結(jié)構(gòu)中,被稱(chēng)之為node table。node table類(lèi)似于:
node table會(huì)在每個(gè)節(jié)點(diǎn)上都保存一份,Redis Cluster通過(guò)gossip協(xié)議把node table復(fù)制到所有的節(jié)點(diǎn)。后面會(huì)繼續(xù)講述node table的復(fù)制。
3、集群成員關(guān)系變更
當(dāng)添加一個(gè)節(jié)點(diǎn)或者刪除一個(gè)節(jié)點(diǎn)時(shí),只需要將命令發(fā)給集群中的任意一個(gè)節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)會(huì)修改本地的node table,并且這個(gè)修改會(huì)最終復(fù)制到所有的節(jié)點(diǎn)上去。添加節(jié)點(diǎn)的命令是CLUSTER MEET,刪除節(jié)點(diǎn)的命令是CLUSTER FORGET。
舉例來(lái)說(shuō)明:
- 打算搭建一個(gè)3個(gè)master節(jié)點(diǎn)的集群,當(dāng)集群創(chuàng)建以前,所有3個(gè)節(jié)點(diǎn)的node table都只包含自己。給其中的一個(gè)節(jié)點(diǎn)A發(fā)送命令,CLUSTER MEET NodeB,節(jié)點(diǎn)A修改自己的node table,將NodeB添加到自己的node table中,并且連接節(jié)點(diǎn)B,把自己的node table發(fā)送給節(jié)點(diǎn)B,節(jié)點(diǎn)B收到節(jié)點(diǎn)A發(fā)送過(guò)來(lái)的node table,會(huì)更新自己的node table,這時(shí)節(jié)點(diǎn)B就知道集群中還有節(jié)點(diǎn)A存在。
這時(shí),給節(jié)點(diǎn)A再發(fā)送CLUSTER MEET NodeC,節(jié)點(diǎn)A會(huì)把節(jié)點(diǎn)C添加到自己的node table,并且把自己的node table復(fù)制給節(jié)點(diǎn)B,節(jié)點(diǎn)B把接收到的node table更新自己的本地的node table,從而知道節(jié)點(diǎn)C的加入。同樣節(jié)點(diǎn)A會(huì)把自己的node table發(fā)給節(jié)點(diǎn)C,節(jié)點(diǎn)C會(huì)更新自己本地的node table,從而知道要加入的集群中已經(jīng)存在節(jié)點(diǎn)A和節(jié)點(diǎn)B。
4、槽
前面說(shuō)過(guò)Redis Cluster會(huì)把數(shù)據(jù)分成多份,也就是把數(shù)據(jù)進(jìn)行分片。Redis Cluster中的每一份數(shù)據(jù)被稱(chēng)為槽(Slot)。Redis Cluster將數(shù)據(jù)拆分成16384份,也就是說(shuō)有16384個(gè)槽。
Redis Cluster采用哈希(Hash)機(jī)制來(lái)拆分?jǐn)?shù)據(jù)。首先,數(shù)據(jù)的key通過(guò)CRC16算法計(jì)算出一個(gè)哈希值。這個(gè)哈希值再對(duì)16384取余,這個(gè)余數(shù)就是槽位,被稱(chēng)為hash slot。具體的CRC16算法可以參看Redis官方文檔。所有余數(shù)相同的key都在一個(gè)slot中,也就是說(shuō),一個(gè)slot其實(shí)就是一批hash余數(shù)相同的key。
每個(gè)hash slot都會(huì)保存在Redis Cluster一個(gè)節(jié)點(diǎn)中。具體哪個(gè)hash slot被保存在哪個(gè)實(shí)例中,就形成了類(lèi)似于一個(gè)map的數(shù)據(jù)結(jié)構(gòu),被稱(chēng)之為hash slot map。hash slot map類(lèi)似于:
- 0 -> NodeA
- 1 -> NodeA
- 2 -> NodeB
- ...
- 16383 -> NodeN
與node table相同,hash slot map也會(huì)在每個(gè)節(jié)點(diǎn)上都會(huì)保存一份,Redis Cluster通過(guò)gossip協(xié)議把hash slot map復(fù)制到所有節(jié)點(diǎn)。同樣,后面還會(huì)講述hash slot map的復(fù)制。
《Redis官方文檔》Redis Cluster Specification, https://redis.io/topics/cluster-spec.
5、數(shù)據(jù)分片變更
要修改數(shù)據(jù)分片關(guān)系,可以連接任意一個(gè)節(jié)點(diǎn),給這個(gè)節(jié)點(diǎn)發(fā)送CLUSTER ADDSLOTS, CLUSTER SETSLOT, CLUSTER DELSLOT命令,修改這個(gè)節(jié)點(diǎn)上的hash slot map,該節(jié)點(diǎn)會(huì)把這個(gè)修改復(fù)制到所有其他節(jié)點(diǎn),其他節(jié)點(diǎn)會(huì)用接收到的hash slot map更新自己的hash slot map。
CLUSTER ADDSLOTS、CLUSTER DELSLOTS、CLUSTER SETSLOT命令的使用如下:
- CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
- CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
- CLUSTER SETSLOT slot NODE node
CLUSTER ADDSLOTS用來(lái)把多個(gè)slot分配給當(dāng)前連接的節(jié)點(diǎn)。例如,連接到節(jié)點(diǎn)A執(zhí)行:
- CLUSTER ADDSLOTS 1 2 3
這個(gè)命令會(huì)把slot1、slot2、slot3分配給節(jié)點(diǎn)A。
CLUSTER DELSLOTS用來(lái)把多個(gè)slot從當(dāng)前連接的節(jié)點(diǎn)刪除。例如,連接到節(jié)點(diǎn)A執(zhí)行:
- CLUSTER DELSLOTS 1 2 3
這個(gè)命令會(huì)把slot1、slot2、slot3從節(jié)點(diǎn)A上刪除。
CLUSTER SETSLOT用來(lái)把一個(gè)slot分配給指定的節(jié)點(diǎn),可以不是當(dāng)前連接的節(jié)點(diǎn),另外這個(gè)命令還可以設(shè)定MIGRATING和IMPORTING兩個(gè)狀態(tài),我們后面再講。例如,連接到節(jié)點(diǎn)A執(zhí)行:
- CLUSTER SETSLOT 1 nodeB
這個(gè)命令會(huì)把slot1分配給節(jié)點(diǎn)B。
6、Slave
一個(gè)slot會(huì)被保存多個(gè)副本,既一個(gè)slot會(huì)保存在多個(gè)節(jié)點(diǎn)上,也就是slot會(huì)復(fù)制到多個(gè)節(jié)點(diǎn)上。Redis Cluster的復(fù)制是以節(jié)點(diǎn)為單位的,一個(gè)節(jié)點(diǎn)上的所有slot會(huì)采用相同的復(fù)制。
具體來(lái)說(shuō)就是,其中一個(gè)節(jié)點(diǎn)會(huì)負(fù)責(zé)處理這個(gè)節(jié)點(diǎn)上所有slot的寫(xiě)操作,這個(gè)節(jié)點(diǎn)被稱(chēng)為master,而其余的節(jié)點(diǎn)被稱(chēng)為slave節(jié)點(diǎn)。一個(gè)master可以有多個(gè)slave。在同一個(gè)節(jié)點(diǎn)上的所有slot的所有的寫(xiě)操作都會(huì)被從master節(jié)點(diǎn)異步復(fù)制到所有的slave節(jié)點(diǎn)。所以slave會(huì)具有與master相同的slot。
通過(guò)SLAVEOF命令來(lái)設(shè)置slave節(jié)點(diǎn)。SLAVEOF命令用來(lái)改變一個(gè)slave節(jié)點(diǎn)的復(fù)制設(shè)置。SLAVEOF命令有兩種格式:
- SLAVEOF NO ONE
- SLAVEOF host port
具體來(lái)講,SLAVEOF NO ONE命令會(huì)停止一個(gè)slave節(jié)點(diǎn)的復(fù)制,并且把這個(gè)slave節(jié)點(diǎn)變成master節(jié)點(diǎn)。SLAVEOF host port命令會(huì)停止一個(gè)slave節(jié)點(diǎn)的復(fù)制,丟棄數(shù)據(jù)集,開(kāi)始從host和port指定的新master節(jié)點(diǎn)復(fù)制。
master和slave的關(guān)系會(huì)被記錄在hash slot table中,相當(dāng)于一個(gè)slot會(huì)映射到多個(gè)節(jié)點(diǎn)上,其中一個(gè)節(jié)點(diǎn)是master,其他記錄的節(jié)點(diǎn)是slave。
加入了master/slave信息后的hash slot map類(lèi)似于:
- 0 -> NodeA,NadeA1(slave)
- 1 -> NodeA,NadeA1(slave)
- 2 -> NodeB,NadeB1(slave)
- ...
- 16383 -> NodeN,NadeN1(slave)
作為hash slot map的一部分,master/slave信息也會(huì)通過(guò)gossip協(xié)議復(fù)制到集群中的每個(gè)節(jié)點(diǎn)。
7、Configuration
node table和hash slot map這兩個(gè)信息,被稱(chēng)之為Configuration,在其他的分布式系統(tǒng)中,也被稱(chēng)之為元數(shù)據(jù)。
前面我們講過(guò),hash slot map和node table在每個(gè)節(jié)點(diǎn)都會(huì)保存一份,并且對(duì)這兩個(gè)信息的任何改動(dòng),都會(huì)通過(guò)gossip協(xié)議傳播(propogate)到所有的節(jié)點(diǎn)。本文我們就不繼續(xù)展開(kāi)gossip協(xié)議了,對(duì)Redis Cluster的gossip協(xié)議的實(shí)現(xiàn)感興趣的同學(xué)可以參看redis的文檔。
在某些分布式系統(tǒng)中,元數(shù)據(jù)被存儲(chǔ)在一個(gè)單獨(dú)的架構(gòu)組件中的。Redis Cluster并沒(méi)有這樣一個(gè)元數(shù)據(jù)存儲(chǔ)的組件,而是把元數(shù)據(jù)分散的存儲(chǔ)在所有的節(jié)點(diǎn)上。
hash slot map和node table在集群創(chuàng)建時(shí)會(huì)被創(chuàng)建出來(lái),并且會(huì)隨著后續(xù)的集群變更(比如failover和擴(kuò)容、縮容等運(yùn)維操作,后面會(huì)講述)而跟隨著變更。變動(dòng)會(huì)在一個(gè)Node上發(fā)起,通過(guò)gossip協(xié)議傳播到所有其他的節(jié)點(diǎn)。這兩個(gè)信息在所有節(jié)點(diǎn)都會(huì)保存,并且會(huì)最終達(dá)到一致。
8、集群創(chuàng)建
創(chuàng)建Redis Cluster時(shí),首先要以cluster模式運(yùn)行多個(gè)redis server,redis server運(yùn)行起來(lái)后,這些server的node id就已經(jīng)生成了。但是這些redis server并沒(méi)有形成集群,也就是server彼此之間并不知道相互的存在。接下來(lái)運(yùn)行CLUSTER MEET命令,讓這些節(jié)點(diǎn)形成一個(gè)集群。但是這時(shí)集群仍然沒(méi)有處于運(yùn)行狀態(tài),需要分配slot,通過(guò)CLUSTER SLOTADD命令把slot分配給具體的節(jié)點(diǎn)之后,集群就可以處理client的命令了。
9、客戶(hù)端的讀寫(xiě)操作
客戶(hù)端要讀取、寫(xiě)入數(shù)據(jù)時(shí),雖然client可以連接任意server,但是實(shí)際中,client需要根據(jù)實(shí)際需求連接到server讀取、寫(xiě)入數(shù)據(jù)。client需要先根據(jù)key計(jì)算出hash slot,連接到負(fù)責(zé)這個(gè)hash slot的節(jié)點(diǎn)進(jìn)行讀寫(xiě)操作。這樣的話(huà),client就要需要知道hash slot -> node的映射關(guān)系,也就是需要知道hash slot map。
前面講過(guò)hash slot map被保存在server端的每個(gè)節(jié)點(diǎn)上。client可以從任意節(jié)點(diǎn)獲取hash slot map,并且把它緩存到client本地,下次操作時(shí)根據(jù)本地緩存直接進(jìn)行操作,但是需要處理緩存信息過(guò)期的問(wèn)題,如果client發(fā)現(xiàn)hash slot map發(fā)生變化(即client讀取寫(xiě)入數(shù)據(jù)時(shí)server回錯(cuò)誤,接下來(lái)會(huì)詳細(xì)講述),會(huì)重新從server端獲取新的hash slot map。通過(guò)hash slot map可以判斷某個(gè)key應(yīng)該存在在哪個(gè)節(jié)點(diǎn)上,client再連接這個(gè)節(jié)點(diǎn)進(jìn)行讀寫(xiě)操作。
10、MOVED Redirection
hash slot map會(huì)發(fā)生變更,這些變更會(huì)復(fù)制到所有的節(jié)點(diǎn),但是gossip保證的是最終復(fù)制到所有的節(jié)點(diǎn),再加上client會(huì)緩存hash slot map,client可能會(huì)把某個(gè)key的請(qǐng)求發(fā)給錯(cuò)誤的節(jié)點(diǎn)來(lái)處理。
錯(cuò)誤的節(jié)點(diǎn)收到請(qǐng)求后,發(fā)現(xiàn)這個(gè)key不應(yīng)該自己來(lái)處理,會(huì)給客戶(hù)端返回MOVED的錯(cuò)誤,在錯(cuò)誤消息中,會(huì)告訴客戶(hù)端,哪個(gè)節(jié)點(diǎn)應(yīng)該負(fù)責(zé)這個(gè)slot。Client收到MOVED消息后,會(huì)向消息中指定的節(jié)點(diǎn)再次發(fā)送請(qǐng)求。
client可以將slot的節(jié)點(diǎn)信息更新到本地緩存的hash slot map中,但是更好的方法是,重新獲取完整的hash slot map,替換本地的緩存。因?yàn)樵诖蠖鄶?shù)情況下,hash slot map中的變更不僅僅只修改一個(gè)slot。
雖然client按照MOVED消息中的節(jié)點(diǎn)信息重新發(fā)送請(qǐng)求,但是client仍然可能再次從新節(jié)點(diǎn)收到MOVED錯(cuò)誤消息,因?yàn)樯弦粋€(gè)節(jié)點(diǎn)的hash slot map可能也不是最新的。但是因?yàn)閔ash slot map最終會(huì)在所有的節(jié)點(diǎn)上一致,所以client在幾次收到MOVED錯(cuò)誤后,最終會(huì)獲取到最新的hash slot map。
11、Failover、currentEpoch、lastVoteEpoch
當(dāng)master發(fā)生故障宕機(jī)后,Redis Cluster會(huì)選出一個(gè)slave來(lái)接替這個(gè)master。
如果有多個(gè)slave存在,那么每個(gè)slave都可能都會(huì)發(fā)現(xiàn)master發(fā)生了宕機(jī),并且試圖把自己變成為master,如果有多個(gè)slave成為master,那么這些新master都會(huì)更新本地hash slot map,把舊master負(fù)責(zé)的slot更新成自己,并且把自己對(duì)hash slot map的更新傳播給其他的節(jié)點(diǎn)。這會(huì)導(dǎo)致hash slot map在節(jié)點(diǎn)間出現(xiàn)差異。
從而導(dǎo)致,因?yàn)樗B接的節(jié)點(diǎn)不同,client拿到不同的hash slot map,對(duì)于同一個(gè)slot,不同的client會(huì)連接不同的節(jié)點(diǎn),最終導(dǎo)致節(jié)點(diǎn)上的數(shù)據(jù)出現(xiàn)差異。所以failover要保證,只有一個(gè)slave被選成新master。
Redis Cluster采用了類(lèi)似Raft算法的技術(shù)來(lái)防止多個(gè)slave被選成master。每個(gè)節(jié)點(diǎn)都會(huì)有叫做currentEpoch、lastVoteEpoch的兩個(gè)值。在集群剛創(chuàng)建時(shí),每個(gè)節(jié)點(diǎn)的currentEpoch都是0。
當(dāng)slave發(fā)現(xiàn)master宕機(jī)時(shí),這個(gè)slave會(huì)增加currentEpoch(即currentEpoch++)。并且向所有的master發(fā)送FAILOVER_AUTH_REQUEST請(qǐng)求,請(qǐng)求中會(huì)攜帶自己currentEpoch,master收到FAILOVER_AUTH_REQUEST,如果請(qǐng)求中的currentEpoch比自己的currentEpoch和lastVoteEpoch都大,則記錄請(qǐng)求中的currentEpoch值到自己的currentEpoch、lastVoteEpoch中,并且回復(fù)FAILOVER_AUTH_ACK給slave,回復(fù)中攜帶master的currentEpoch。
所以可以看出,F(xiàn)AILOVER_AUTH_ACK中的Epoch一定與slave的currentEpoch相同。Slave從大多數(shù)的master收到FAILOVER_AUTH_ACK后,則成為master。
上面的過(guò)程保證只有一個(gè)slave被選出,我們來(lái)舉例說(shuō)明。五個(gè)master的集群,master節(jié)點(diǎn)分別是A、B、C、D、E,A節(jié)點(diǎn)有兩個(gè)slave,分別是A1和A2。A節(jié)點(diǎn)發(fā)生宕機(jī),A1增加自己的currentEpoch=5(4+1),A1給所有的master發(fā)送FAILOVER_AUTH_REQUEST,節(jié)點(diǎn)B、C、D收到FAILOVER_AUTH_REQUEST,把自己的currentEpoch和lastVoteEpoch更新成5,并且給A1回復(fù)FAILOVER_AUTH_ACK,A1贏得選舉,成為新的master。但是與此同時(shí),A2也發(fā)現(xiàn)A宕機(jī),也試圖選舉成master。A2增加自己的currentEpoch=5(4+1),A2給所有的master發(fā)送FAILOVER_AUTH_REQUEST,但這時(shí)的B、C、D的lastVoteEpoch已經(jīng)是5,所以B、C、D不會(huì)給A2回復(fù),E還沒(méi)有收到A1的請(qǐng)求,所以只有E會(huì)給A2回復(fù),但是不能形成大多數(shù),所以A2不能稱(chēng)為master。
上面講述的過(guò)程已經(jīng)可以保證只有一個(gè)master被選出,但是除此之外,Redis Cluster還做了一個(gè)優(yōu)化,那就是master回復(fù)了一個(gè)請(qǐng)求后不會(huì)在給這個(gè)master的其他的slave發(fā)送回復(fù)。
12、Configuration epoch
failover完成后,新master會(huì)修改hash slot map,把相應(yīng)的slot記錄的節(jié)點(diǎn)改成自己,并且把這次對(duì)hash slot map的改動(dòng)傳播給其他節(jié)點(diǎn)。
雖然currentEpoch和lastVoteEpoch能保證每次failover只能有一個(gè)節(jié)點(diǎn)被選成新的master,但是先后兩次failover,可能選出的兩個(gè)不同的master,但是他們對(duì)hash slot map的修改的傳播卻是異步,也就是后面一次failover的改動(dòng)可能先于第一次failover的改動(dòng)到達(dá)某個(gè)節(jié)點(diǎn),從而導(dǎo)致節(jié)點(diǎn)間對(duì)hash slot map這個(gè)信息產(chǎn)生不一致。
Redis Cluster通過(guò)configEpoch來(lái)解決這個(gè)問(wèn)題。每個(gè)節(jié)點(diǎn)會(huì)保存一個(gè)configEpoch的值。相當(dāng)于在node table中還會(huì)有一列數(shù)據(jù)叫configEpoch,類(lèi)似于下面的表:
每次failover完成后,新選出的master會(huì)用currentEpoch覆蓋configEpoch。Failover的機(jī)制保證兩次failover的新master一定是具有不同的currentEpoch,并且后一次的failover的currentEpoch一定比前一次大。這樣就可以保證,即便采用了gossip這樣傳播協(xié)議,仍然能夠保證最后一次failover的hash slot map的變更會(huì)生效,也就是configEpoch更大的變更會(huì)生效,并且最終所有的節(jié)點(diǎn)上的hash slot map是一致的。
Slave節(jié)點(diǎn)的configEpoch就是其master的configEpoch。
由于gossip保證的hash slot map最終保持一致的,所以可能存在slave的hash slot map舊于master,failover不能基于舊的hash slot map基礎(chǔ)上做變更,所以前面failover的過(guò)程中還需要補(bǔ)充一個(gè)規(guī)則要遵守:
- 在FAILOVER_AUTH_REQUEST中會(huì)攜帶slave節(jié)點(diǎn)的configEpoch,如果這個(gè)slave的configEpoch比這個(gè)slave負(fù)責(zé)的所有slot的master的configEpoch中任意一個(gè)要小,則master不會(huì)給slave回復(fù)FAILOVER_AUTH_ACK。
13、Resharding
在集群創(chuàng)建之后,我們還會(huì)有對(duì)Redis cluster做擴(kuò)容、縮容、balancing這樣的運(yùn)維需求,這些需求本質(zhì)上都可以用 Resharding 操作解決,resharding操作就是把slot在節(jié)點(diǎn)間重新的分布,把slot從一個(gè)節(jié)點(diǎn)轉(zhuǎn)移到另外一個(gè)節(jié)點(diǎn)上。在Redis Cluster中,擴(kuò)容需求實(shí)質(zhì)上就是加入一個(gè)新的節(jié)點(diǎn),再把一些slot分配到這個(gè)新節(jié)點(diǎn)上。
縮容需求實(shí)質(zhì)上就是先把這個(gè)節(jié)點(diǎn)上的所有slot分配到其他節(jié)點(diǎn)上,再把這個(gè)節(jié)點(diǎn)從集群中移出。當(dāng)節(jié)點(diǎn)間的流量不均衡時(shí),我們有balancing這樣的需求,balancing就是把流量比較大的節(jié)點(diǎn)上的一些slot分配都流量比較少的節(jié)點(diǎn)上。
Resharding操作可以是對(duì)整個(gè)hash slot map的調(diào)整,也就是可以包括對(duì)多個(gè)slot的遷移(migration),遷移就是把一個(gè)slot從一個(gè)節(jié)點(diǎn)遷移到另外一個(gè)節(jié)點(diǎn)。一個(gè)slot migration操作包括前面講的hash slot map變更,另外還包括key的遷移操作。要把一個(gè)slot遷移到另外的節(jié)點(diǎn)上,首先把這個(gè)slot上的所有的key遷移到這個(gè)節(jié)點(diǎn),當(dāng)把所有key都遷移完后,再進(jìn)行hash slot map變更,當(dāng)hash slot map變更完成,這次slot migration結(jié)束。
Redis Cluster使用CLUSTER SETSLOT來(lái)設(shè)置遷移。舉例說(shuō)明,將slot1從節(jié)點(diǎn)A遷移到節(jié)點(diǎn)B。分別對(duì)節(jié)點(diǎn)A和節(jié)點(diǎn)B執(zhí)行下面的命令:
- 節(jié)點(diǎn)A上:CLUSTER SETSLOT 1 MIGRATING NODEB
- 節(jié)點(diǎn)B上:CLUSTER SETSLOT 1 IMPORTING NODEA
其中,MIGRATING表示數(shù)據(jù)要從這個(gè)節(jié)點(diǎn)遷出,而IMPORTING表示數(shù)據(jù)要往這個(gè)節(jié)點(diǎn)遷入。
執(zhí)行完這兩個(gè)命令后,節(jié)點(diǎn)A中的slot1不在創(chuàng)建新的key。一個(gè)叫做redis-trib的特殊的程序負(fù)責(zé)把所有的key從節(jié)點(diǎn)A遷移到節(jié)點(diǎn)B。redis-trib會(huì)執(zhí)行下面的命令:
- CLUSTER GETKEYSINSLOT slot count
這個(gè)命令會(huì)返回count個(gè)key,對(duì)于每個(gè)返回的key,redis-trib執(zhí)行下面的命令:
- MIGRATE target_host target_port key target_database id timeout
這個(gè)命令會(huì)原子地把一個(gè)key從節(jié)點(diǎn)A遷移到節(jié)點(diǎn)B。具體來(lái)說(shuō),MIGRATE命令會(huì)連接目標(biāo)節(jié)點(diǎn),并發(fā)向目標(biāo)節(jié)點(diǎn)發(fā)送這個(gè)key,一旦目標(biāo)節(jié)點(diǎn)收到這個(gè)key,則從自己的數(shù)據(jù)庫(kù)中刪除這個(gè)key,在這個(gè)過(guò)程中,節(jié)點(diǎn)A和節(jié)點(diǎn)B都會(huì)加鎖。
在把所有的key遷移完后,再分別在兩個(gè)節(jié)點(diǎn)上執(zhí)行下面的命令:
- CLUSTER SETSLOT slot NODE nodeA
把所有的key遷移完一般需要一些時(shí)間,也就是說(shuō)在開(kāi)始遷移后和完成遷移前,在這個(gè)窗口期內(nèi),key的實(shí)際的分布,與hash slot map里記錄的是不一致的,client按照hash slot map訪(fǎng)問(wèn)key,會(huì)出現(xiàn)錯(cuò)誤。
Redis Cluster通過(guò)ASK redirection來(lái)解決這個(gè)問(wèn)題。按照client端的hash slot map,slot1的key一定會(huì)發(fā)給節(jié)點(diǎn)A,節(jié)點(diǎn)A收到這個(gè)請(qǐng)求后,如果發(fā)現(xiàn)這個(gè)key已經(jīng)遷移到節(jié)點(diǎn)B了,那么就會(huì)給client回復(fù)ASK redirection,client收到ASK redirection后,會(huì)向節(jié)點(diǎn)b先發(fā)送一個(gè)ASKING命令,之后在發(fā)送對(duì)這個(gè)key的請(qǐng)求。
14、Configuration的實(shí)際存儲(chǔ)
Hash slot map和node table都是邏輯上的結(jié)構(gòu),他們?cè)赗edis Cluster中的實(shí)際存儲(chǔ)結(jié)構(gòu)稍有不同(詳情看結(jié)尾參考資料1、2、3、4)。
在節(jié)點(diǎn)的內(nèi)存中,用兩個(gè)變量來(lái)存儲(chǔ)這兩個(gè)信息:
- myself變量:myself代表本節(jié)點(diǎn),是一個(gè)ClusterNode類(lèi)型的變量,這個(gè)變量中,包含本節(jié)點(diǎn)的configEpoch,還包括slaveof,如果是slave節(jié)點(diǎn)則在slaveof中記錄著它的master節(jié)點(diǎn),還包括一個(gè)bitmap,代表這個(gè)節(jié)點(diǎn)負(fù)責(zé)的所有的slot的槽位值。這個(gè)bitmap有2048個(gè)byte組成,一總是16384(2048*8)個(gè)bit,每個(gè)bit代表一個(gè)slot,bit置1,代表這個(gè)節(jié)點(diǎn)負(fù)責(zé)這個(gè)slot;
- cluster變量:代表了所在集群的狀態(tài),它包含currentEpoch、lastVoteEpoch和slots數(shù)組,slots數(shù)組的index代表了slot,數(shù)組的每個(gè)成員都指向一個(gè)節(jié)點(diǎn),是一個(gè)ClusterNode類(lèi)型的變量,與myself變量的類(lèi)型一樣。
所有Configuration的更改都會(huì)被保存到磁盤(pán)中,具體來(lái)講是保存到一個(gè)名字叫node.conf的文件中,這個(gè)文件是Redis Cluster負(fù)責(zé)寫(xiě)入的,不需要人工配置。
node.conf按照節(jié)點(diǎn)維度進(jìn)行保存。每一行對(duì)應(yīng)一個(gè)節(jié)點(diǎn),每行分別包含這些信息:id,ip:port,flag,slaveof,ping timestamp, pong timespamp,configEpoch,link status,slots。
所有的節(jié)點(diǎn)結(jié)束后,會(huì)在文件的最后保存curruntEpoch和lastVoteEpoch兩個(gè)變量。其中flag字段是枚舉類(lèi)型,會(huì)指明這個(gè)節(jié)點(diǎn)是不是自己,節(jié)點(diǎn)類(lèi)型是master還是slave。
如果是slave節(jié)點(diǎn),則會(huì)在slaveof字段記錄其master節(jié)點(diǎn)的id。如果是master節(jié)點(diǎn),則在最后多一個(gè)slots字段,記錄著這個(gè)節(jié)點(diǎn)負(fù)責(zé)著哪些slot。Flags字段還記錄著其他非常重
要的狀態(tài),本文就不繼續(xù)展開(kāi)了。
同樣,ping timestamp、pong timestmap、link staus三個(gè)字段本文也不繼續(xù)展開(kāi)了。
具體的node.conf文件類(lèi)似下面的例子:
- [root@10.112.178.141 data]# cat nodes-6384.conf
- fb763117270d14205c41174605b15741co03a945 10.112.178.174:6383 slave 5e35bda1a44c8d781eb54e08be88a3bab42070f3 0 1596683852819 2 connected
- 3dc5890fb1591e3b20196f81eb5f2f99754253e8 10.112.178.141:6383 master - 0 1596683851915 1 connected 0-5461
- f1967b687c9b2c27108cce08517e98e7a80d5e7e 10.112.178.171:6383 slave 3dc5890fb1591e3b20196f81eb5f2f99754253e8 0 1596683850813 1 connected
- 2bbab7353e973e991566df3bb52afb4857a7bf25 10.112.178.171:6384 slave 1f0a8cf1bfd0c915ef404482f3dc6bf5c7cf41f5 0 1596683848812 3 connected
- 5e35bda1a44c8d781eb54e08be88a3bab42070f3 10.112.178.142:6383 master - 0 1596683849813 2 connected 5462-10923
- 1f0a8cf1bfd0c915ef404482f3dc6bf5c7cf41f5 10.112.178.141:6384 myself,master - 0 0 3 connected 10924-16383
節(jié)點(diǎn)啟動(dòng)時(shí)會(huì)讀取node.conf文件,把里面的信息加載到myself和cluster兩個(gè)變量中。Slot信息會(huì)被轉(zhuǎn)換成bitmap保存在myself變量中。并且slot信息還會(huì)逆向的轉(zhuǎn)換成slot到節(jié)點(diǎn)的映射保存在cluster變量中。
hash slot map變更或者node table變更,就是修改內(nèi)存中的myself變量和cluater變量,并且每次變更都會(huì)把這兩個(gè)變量序列化轉(zhuǎn)化后保存到node.conf中。
15、查看configuration
Redis Cluster提供了兩個(gè)命令來(lái)查看configuration:
第一個(gè)是CLUSTER SLOT命令,用來(lái)展示hash slot維度的信息,CLUSTER SLOT命令的展示如下:
- 127.0.0.1:7000> cluster slots
- 1) 1) (integer) 5461
- 2) (integer) 10922
- 3) 1) "127.0.0.1"
- 2) (integer) 7001
- 4) 1) "127.0.0.1"
- 2) (integer) 7004
- 2) 1) (integer) 0
- 2) (integer) 5460
- 3) 1) "127.0.0.1"
- 2) (integer) 7000
- 4) 1) "127.0.0.1"
- 2) (integer) 7003
- 3) 1) (integer) 10923
- 2) (integer) 16383
- 3) 1) "127.0.0.1"
- 2) (integer) 7002
- 4) 1) "127.0.0.1"
- 2) (integer) 7005
第二個(gè)是CLUSTER NODE命令,用來(lái)展示node table維度的信息,CLUSTER NODE命令的展示如下:
- $ redis-cli cluster nodes
- d1861060fe6a534d42d8a19aeb36600e18785e04 127.0.0.1:6379 myself - 0 1318428930 1 connected 0-1364
- 3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 2 connected 1365-2729
- d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 3 connected 2730-4095
CLUSTER NODE、CLUSTER SLOT兩個(gè)命令可以連接到任意節(jié)點(diǎn)上執(zhí)行,這兩個(gè)命令都是讀取的這個(gè)節(jié)點(diǎn)的本地信息,根據(jù)gossip的特性,存在這兩個(gè)命令展示的不是最新的configuration的可能性。
16、Conflict
雖然前面講的failover過(guò)程通過(guò)大多數(shù)master投票的方式保證只有一個(gè)slave選中,并且產(chǎn)生唯一的configEpoch。但是Resharding的過(guò)程卻沒(méi)有經(jīng)過(guò)大多數(shù)的master的投票。
執(zhí)行slot遷移時(shí),僅僅是在集群中所有configEpoch中最大的那個(gè)configEpoch的基礎(chǔ)上,再加一而得到的。并且由于Resharding一般包括多個(gè)slot的遷移,Redis cluster目前的做法是,在一次resharding過(guò)程中,所有的slot遷移使用的configEpoch都是第一個(gè)slot遷移時(shí)產(chǎn)生的那個(gè)configEpoch。
而failover和resharding都會(huì)修改hash slot map,如果在resharding的過(guò)程中發(fā)生了failover,這就有可能導(dǎo)致對(duì)hash slot map的修改產(chǎn)生沖突。另外,手動(dòng)failover也是不經(jīng)過(guò)master投票的,也就是執(zhí)行CLUSTER FAILOVER命令(帶TAKEOVER參數(shù))。
產(chǎn)生沖突就是指針對(duì)同一個(gè)slot,slot被修改成映射到不同的節(jié)點(diǎn)上,并且這些修改具有相同的configEpoch。
為了解決這個(gè)問(wèn)題,Redis cluster需要存在一個(gè)沖突解決的機(jī)制。如果一個(gè)master發(fā)現(xiàn)相同的configEpoch,則比較一下兩個(gè)節(jié)點(diǎn)的id,id小的節(jié)點(diǎn),把自己currentEpoch加一,作為自己的configEpoch。
三、Write Safety
由于有沖突的存在,可能導(dǎo)致不同的節(jié)點(diǎn)上的hash slot map不一致,取決于連接的節(jié)點(diǎn)不同,一部分client可能會(huì)把某個(gè)slot的key寫(xiě)入到一個(gè)節(jié)點(diǎn)中,而另外一部分client會(huì)把同樣slot的key寫(xiě)入到另外一個(gè)節(jié)點(diǎn)中。當(dāng)沖突被解決后,其中一個(gè)節(jié)點(diǎn)上接受的寫(xiě)入會(huì)丟失。
另外,由于master和slave之間的數(shù)據(jù)復(fù)制是異步的,在failover時(shí)如果slave還沒(méi)有收到最新的數(shù)據(jù),就發(fā)生了failover,那么這部分寫(xiě)入就會(huì)丟失。Redis cluster在這方面做了一個(gè)優(yōu)化,當(dāng)一個(gè)slave發(fā)現(xiàn)master發(fā)生了宕機(jī),它不會(huì)立即開(kāi)始選舉的過(guò)程,它會(huì)等待一個(gè)時(shí)間,這個(gè)時(shí)間計(jì)算公式如下:
- DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds
這個(gè)計(jì)算公式中,
- 第一部分是一個(gè)固定500毫秒的時(shí)間,這是為了給master充分的時(shí)間,也發(fā)現(xiàn)節(jié)點(diǎn)宕機(jī)這個(gè)事實(shí);
- 第二個(gè)是隨機(jī)等待一段時(shí)間,這是為了盡量避免多個(gè)slave同時(shí)發(fā)現(xiàn)master宕機(jī),然后同時(shí)開(kāi)始選舉,導(dǎo)致master被瓜分,從而導(dǎo)致所有的選舉都不成功;
- 第三部分是slave的rank,rank主要取決于slave的復(fù)制進(jìn)度,復(fù)制的數(shù)據(jù)越多,則rank越小,也就是越短的等待時(shí)間,越先開(kāi)始選舉,有更大的可能性被選成新的master。但這僅僅是個(gè)優(yōu)化,不能完全防止丟數(shù)據(jù)的可能。
作者介紹
陳東明,現(xiàn)任國(guó)美在線(xiàn)基礎(chǔ)架構(gòu)總監(jiān)。曾任餓了么北京技術(shù)中心任架構(gòu)組負(fù)責(zé)人,負(fù)責(zé)產(chǎn)品線(xiàn)架構(gòu)設(shè)計(jì)及基礎(chǔ)架構(gòu)研發(fā)工作;曾任百度架構(gòu)師,負(fù)責(zé)即時(shí)通訊產(chǎn)品的架構(gòu)設(shè)計(jì)。具有豐富的大規(guī)模系統(tǒng)構(gòu)建和基礎(chǔ)架構(gòu)研發(fā)經(jīng)驗(yàn),善于復(fù)雜業(yè)務(wù)需求下的大并發(fā)、分布式系統(tǒng)設(shè)計(jì)和持續(xù)優(yōu)化。個(gè)人公眾號(hào):dongming_cdm。
當(dāng)前文章:拆解RedisCluster,怎么實(shí)現(xiàn)“寫(xiě)安全”這個(gè)重要特性?
文章URL:http://www.dlmjj.cn/article/cdheigs.html


咨詢(xún)
建站咨詢(xún)
