Redis 高可用集群原理和實(shí)踐
微信公眾號(hào):運(yùn)維開發(fā)故事,作者:鄭哥
Redis 集群是 Redis 提供的分布式數(shù)據(jù)庫方案,集群通過分片(sharding)來進(jìn)行數(shù)據(jù)共享,并提供復(fù)制和故障轉(zhuǎn)移能力。
集群環(huán)境搭建
Redis 集群最少需要 3 個(gè) master 節(jié)點(diǎn),這里我們搭建 3 個(gè)master 節(jié)點(diǎn),3 個(gè) slave 及節(jié)點(diǎn)(由于我機(jī)器配置受限,直接通過端口的方式模擬集群搭建,本處只是實(shí)驗(yàn)方便,生產(chǎn)環(huán)境不可采取此方案)。
環(huán)境搭建步驟如下:
- 簡(jiǎn)單說明,首先我們先要定義集群節(jié)點(diǎn)的端口
7000-7005
然后配置文件復(fù)制 redis.conf 到對(duì)應(yīng)的配置文件名。
IP | 端口 | 配置文件 |
---|---|---|
127.0.0.1 | 7000 | 7000/redis-7000.conf |
127.0.0.1 | 7001 | 7001/redis-7001.conf |
127.0.0.1 | 7002 | 7002/redis-7002.conf |
127.0.0.1 | 7003 | 7003/redis-7003.conf |
127.0.0.1 | 7004 | 7004/redis-7004.conf |
127.0.0.1 | 7005 | 7005/redis-7005.conf |
- 編輯 redis.conf 文件,主要修改以下的幾個(gè)配置(如果需要設(shè)置密碼需要配置
requirepass
和masterauth
)
daemonize yes
# 這個(gè)端口和上面的配置清單一致即可
port 7000
# 啟動(dòng)集群模式
cluster-enabled yes
# 集群節(jié)點(diǎn)信息文件,這里700x最好和port對(duì)應(yīng)上
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
appendonly yes
- 服務(wù)啟動(dòng),注意我們需要啟動(dòng)所有的節(jié)點(diǎn),命令如下:
# 啟動(dòng)所有的服務(wù) 7000-7005 cd 7000 redis-server ./redis-7000.conf
- 初始化集群,通過
redis-cli --cluster create
命令初始化集群,命令如下(如果是生產(chǎn)環(huán)境,需要節(jié)點(diǎn)間 IP 以及端口是否可互相訪問):
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1
- 集群狀態(tài)查詢
-
登錄節(jié)點(diǎn)
redis-cli -c -h 127.0.0.1 -p 7000
, 注意一定要加-c
表示集群模式。 -
查詢集群狀態(tài)
cluster info
? etc redis-cli -c -h 127.0.0.1 -p 7000 127.0.0.1:7000> 127.0.0.1:7000> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:6 cluster_my_epoch:1 cluster_stats_messages_ping_sent:263 cluster_stats_messages_pong_sent:270 cluster_stats_messages_sent:533 cluster_stats_messages_ping_received:270 cluster_stats_messages_pong_received:263 cluster_stats_messages_received:533 127.0.0.1:7000>
其他的集群創(chuàng)建方案 utils/create-cluster
我們可以在參考資料中找到創(chuàng)建方式(參考文檔:https://redis.io/docs/manual/scaling/#redis-cluster-101)
集群原理
槽指派機(jī)制
Redis Cluster 將所有數(shù)據(jù)劃分為 16384 個(gè) slots(槽位),每個(gè)節(jié)點(diǎn)負(fù)責(zé)其中一部分槽位。槽位的信息存儲(chǔ)于每個(gè)節(jié)點(diǎn)中。
當(dāng) Redis Cluster 的客戶端來連接集群時(shí),它也會(huì)得到一份集群的槽位配置信息并將其緩存在客戶端本地。這樣當(dāng)客戶端要查找某個(gè) key 時(shí),可以直接定位到目標(biāo)節(jié)點(diǎn)。同時(shí)因?yàn)椴畚坏男畔⒖赡軙?huì)存在客戶端與服務(wù)器不一致的情況,還需要糾正機(jī)制來實(shí)現(xiàn)槽位信息的校驗(yàn)調(diào)整。
槽位定位算法
Cluster 默認(rèn)會(huì)對(duì) key 值使用 crc16 算法進(jìn)行 hash 得到一個(gè)整數(shù)值,然后用這個(gè)整數(shù)值對(duì) 16384 進(jìn)行與操作來得到具體槽位。
源碼位置 src/cluster.c
中的 keyHashSlot 方法
crc16(key,keylen) & 0x3FFF
為什是 16384 可以看看這篇文章 :Redis 為什么是 16384 個(gè)槽 ?
查詢某個(gè) key 那個(gè)節(jié)點(diǎn)上,方法如下:
# 查詢 key 所在的槽位
127.0.0.1:7000> cluster keyslot wahaha
(integer) 12318
# 查詢所有的 槽分布信息
# 可以得出結(jié)論:wahaha 這個(gè) key 在 7002 這個(gè)節(jié)點(diǎn)上
127.0.0.1:7000> cluster slots
1) 1) (integer) 10923
2) (integer) 16383
3) 1) "127.0.0.1"
2) (integer) 7002
3) "de631f8ac9649f5d9fb12013dc01407f953c3299"
4) 1) "127.0.0.1"
2) (integer) 7004
3) "6ac6065d07eb44b674a181da897401ec4cea9571"
2) 1) (integer) 5461
2) (integer) 10922
3) 1) "127.0.0.1"
2) (integer) 7001
3) "e36fc81472afb04b9c88af1504a8e02647de1b13"
4) 1) "127.0.0.1"
2) (integer) 7003
3) "a89dcff90147f6cc9425ff0c6e4bead7017dc1e1"
3) 1) (integer) 0
2) (integer) 5460
3) 1) "127.0.0.1"
2) (integer) 7000
3) "0acfc8b3dd2223333a03bbcf856dd2a839d2e072"
4) 1) "127.0.0.1"
2) (integer) 7005
3) "57ddd19f38d8b748386944d15d32671fb5cb1570"
127.0.0.1:7000>
其實(shí)沒有這么麻煩,集群模式支持 跳轉(zhuǎn)重定位 我們直接 get 就可以跳轉(zhuǎn)過去。
跳轉(zhuǎn)重定位
當(dāng)客戶端向一個(gè)錯(cuò)誤的節(jié)點(diǎn)發(fā)出了指令,該節(jié)點(diǎn)會(huì)發(fā)現(xiàn)指令的 key 所在的槽位并不歸自己管理,這時(shí)它會(huì)向客戶端發(fā)送一個(gè)特殊的跳轉(zhuǎn)指令攜帶目標(biāo)操作的節(jié)點(diǎn)地址,告訴客戶端去連這個(gè)節(jié)點(diǎn)去獲取數(shù)據(jù)。客戶端收到指令后除了跳轉(zhuǎn)到正確的節(jié)點(diǎn)上去操作,還會(huì)同步更新糾正本地的槽位映射表緩存,后續(xù)所有 key 將使用新的槽位映射表。
大白話說:就是如果當(dāng)前 key 是自己的節(jié)點(diǎn)的槽位就自己處理,如果不是自己的槽位,就轉(zhuǎn)向目標(biāo)槽位的節(jié)點(diǎn)。
演示一下:
set abc sdl
set sbc sdl
image.png
重定位的過程如下:
集群通訊機(jī)制
redis cluster 節(jié)點(diǎn)間采取 gossip 協(xié)議進(jìn)行通信
維護(hù)集群的元數(shù)據(jù)(集群節(jié)點(diǎn)信息,主從角色,節(jié)點(diǎn)數(shù)量,各節(jié)點(diǎn)共享的數(shù)據(jù)等)有兩種方式:
-
集中式
-
gossip
集中式
優(yōu)點(diǎn)在于元數(shù)據(jù)的更新和讀取,時(shí)效性非常好,一旦元數(shù)據(jù)出現(xiàn)變更立即就會(huì)更新到集中式的存儲(chǔ)中,其他節(jié)點(diǎn)讀取的時(shí)候立即就可以立即感知到;不足在于所有的元數(shù)據(jù)的更新壓力全部集中在一個(gè)地方,可能導(dǎo)致元數(shù)據(jù)的存儲(chǔ)壓力。很多中間件都會(huì)借助 zookeeper 集中式存儲(chǔ)元數(shù)據(jù)。
gossip
gossip 協(xié)議包含多種消息,包括ping,pong,meet,fail等等。
meet:某個(gè)節(jié)點(diǎn)發(fā)送meet給新加入的節(jié)點(diǎn),讓新節(jié)點(diǎn)加入集群中,然后新節(jié)點(diǎn)就會(huì)開始與其他節(jié)點(diǎn)進(jìn)行通信;
ping:每個(gè)節(jié)點(diǎn)都會(huì)頻繁給其他節(jié)點(diǎn)發(fā)送ping,其中包含自己的狀態(tài)還有自己維護(hù)的集群元數(shù)據(jù),互相通過ping交換元數(shù)據(jù)(類似自己感知到的集群節(jié)點(diǎn)增加和移除,hash slot信息等);
pong: 對(duì)ping和meet消息的返回,包含自己的狀態(tài)和其他信息,也可以用于信息廣播和更新;
fail: 某個(gè)節(jié)點(diǎn)判斷另一個(gè)節(jié)點(diǎn)fail之后,就發(fā)送fail給其他節(jié)點(diǎn),通知其他節(jié)點(diǎn),指定的節(jié)點(diǎn)宕機(jī)了。
gossip協(xié)議的優(yōu)點(diǎn)在于元數(shù)據(jù)的更新比較分散,不是集中在一個(gè)地方,更新請(qǐng)求會(huì)陸陸續(xù)續(xù),打到所有節(jié)點(diǎn)上去更新,有一定的延時(shí),降低了壓力;缺點(diǎn)在于元數(shù)據(jù)更新有延時(shí)可能導(dǎo)致集群的一些操作會(huì)有一些滯后。
gossip 通信的 10000 端口
每個(gè)節(jié)點(diǎn)都有一個(gè)專門用于節(jié)點(diǎn)間gossip通信的端口,就是自己提供服務(wù)的端口號(hào)+10000,比如7001,那么用于節(jié)點(diǎn)間通信的就是17001端口。每個(gè)節(jié)點(diǎn)每隔一段時(shí)間都會(huì)往另外幾個(gè)節(jié)點(diǎn)發(fā)送ping消息,同時(shí)其他幾點(diǎn)接收到ping消息之后返回pong消息。
網(wǎng)絡(luò)抖動(dòng)
真實(shí)世界的機(jī)房網(wǎng)絡(luò)往往并不是風(fēng)平浪靜的,它們經(jīng)常會(huì)發(fā)生各種各樣的小問題。比如網(wǎng)絡(luò)抖動(dòng)就是非常常見的一種現(xiàn)象,突然之間部分連接變得不可訪問,然后很快又恢復(fù)正常。
為解決這種問題,Redis Cluster 提供了一種選項(xiàng)cluster-node-timeout,表示當(dāng)某個(gè)節(jié)點(diǎn)持續(xù) timeout 的時(shí)間失聯(lián)時(shí),才可以認(rèn)定該節(jié)點(diǎn)出現(xiàn)故障,需要進(jìn)行主從切換。如果沒有這個(gè)選項(xiàng),網(wǎng)絡(luò)抖動(dòng)會(huì)導(dǎo)致主從頻繁切換 (數(shù)據(jù)的重新復(fù)制)。
集群選舉原理
當(dāng)slave發(fā)現(xiàn)自己的master變?yōu)镕AIL狀態(tài)時(shí),便嘗試進(jìn)行Failover,以期成為新的master。由于掛掉的master可能會(huì)有多個(gè)slave,從而存在多個(gè)slave競(jìng)爭(zhēng)成為master節(jié)點(diǎn)的過程, 其過程如下:
-
slave發(fā)現(xiàn)自己的master變?yōu)镕AIL
-
將自己記錄的集群currentEpoch加1,并廣播FAILOVER_AUTH_REQUEST 信息
-
其他節(jié)點(diǎn)收到該信息,只有master響應(yīng),判斷請(qǐng)求者的合法性,并發(fā)送FAILOVER_AUTH_ACK,對(duì)每一個(gè)epoch只發(fā)送一次ack
-
嘗試failover的slave收集master返回的FAILOVER_AUTH_ACK
-
slave收到超過半數(shù)master的ack后變成新Master(這里解釋了集群為什么至少需要三個(gè)主節(jié)點(diǎn),如果只有兩個(gè),當(dāng)其中一個(gè)掛了,只剩一個(gè)主節(jié)點(diǎn)是不能選舉成功的)
-
slave廣播Pong消息通知其他集群節(jié)點(diǎn)。
從節(jié)點(diǎn)并不是在主節(jié)點(diǎn)一進(jìn)入 FAIL 狀態(tài)就馬上嘗試發(fā)起選舉,而是有一定延遲,一定的延遲確保我們等待FAIL狀態(tài)在集群中傳播,slave如果立即嘗試選舉,其它masters或許尚未意識(shí)到FAIL狀態(tài),可能會(huì)拒絕投票
延遲計(jì)算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已經(jīng)從master復(fù)制數(shù)據(jù)的總量的rank。Rank越小代表已復(fù)制的數(shù)據(jù)越新。這種方式下,持有最新數(shù)據(jù)的slave將會(huì)首先發(fā)起選舉(理論上)。
集群腦裂數(shù)據(jù)丟失問題
Redis 集群沒有過半機(jī)制會(huì)有腦裂問題,網(wǎng)絡(luò)分區(qū)導(dǎo)致腦裂后多個(gè)主節(jié)點(diǎn)對(duì)外提供寫服務(wù),一旦網(wǎng)絡(luò)分區(qū)恢復(fù),會(huì)將其中一個(gè)主節(jié)點(diǎn)變?yōu)閺墓?jié)點(diǎn),這時(shí)會(huì)有大量數(shù)據(jù)丟失。
規(guī)避方法可以在 redis 配置里加上參數(shù)(這種方法不可能百分百避免數(shù)據(jù)丟失,參考集群leader選舉機(jī)制):
// 寫數(shù)據(jù)成功最少同步的slave數(shù)量,這個(gè)數(shù)量可以模仿大于半數(shù)機(jī)制配置, // 比如集群總共三個(gè)節(jié)點(diǎn)可以配置1,加上leader就是2,超過了半數(shù) min-replicas-to-write 1
注意:這個(gè)配置在一定程度上會(huì)影響集群的可用性,比如slave要是少于1個(gè),這個(gè)集群就算leader正常也不能提供服務(wù)了,需要具體場(chǎng)景權(quán)衡選擇。
集群是否完整才能對(duì)外提供服務(wù)
當(dāng)redis.conf的配置cluster-require-full-coverage為no時(shí),表示當(dāng)負(fù)責(zé)一個(gè)插槽的主庫下線
且沒有相應(yīng)的從庫進(jìn)行故障恢復(fù)時(shí),集群仍然可用,如果為yes則集群不可用(默認(rèn)為 yes)。
Redis 集群為什么至少需要三個(gè)master節(jié)點(diǎn),并且推薦節(jié)點(diǎn)數(shù)為奇數(shù)?
因?yàn)樾耺aster的選舉需要大于半數(shù)的集群master節(jié)點(diǎn)同意才能選舉成功,如果只有兩個(gè)master節(jié)點(diǎn),當(dāng)其中一個(gè)掛了,是達(dá)不到選舉新master的條件的。
奇數(shù)個(gè)master節(jié)點(diǎn)可以在滿足選舉該條件的基礎(chǔ)上節(jié)省一個(gè)節(jié)點(diǎn),比如三個(gè)master節(jié)點(diǎn)和四個(gè)master節(jié)點(diǎn)的集群相比,大家如果都掛了一個(gè)master節(jié)點(diǎn)都能選舉新master節(jié)點(diǎn),如果都掛了兩個(gè)master節(jié)點(diǎn)都沒法選舉新master節(jié)點(diǎn)了,所以奇數(shù)的master節(jié)點(diǎn)更多的是從節(jié)省機(jī)器資源角度出發(fā)說的。
Redis 集群對(duì)批量操作命令的支持
如何讓多個(gè) key 落到一個(gè)槽里面 ?
對(duì)于類似mset,mget這樣的多個(gè)key的原生批量操作命令,redis集群只支持所有key落在同一slot的情況,如果有多個(gè)key一定要用mset命令在redis集群上操作,則可以在key的前面加上{XX},這樣參數(shù)數(shù)據(jù)分片hash計(jì)算的只會(huì)是大括號(hào)里的值,這樣能確保不同的key能落到同一slot里去,示例如下:
mset {user1}:1:name zhangsan {user1}:1:age 18
假設(shè)name和age計(jì)算的hash slot值不一樣,但是這條命令在集群下執(zhí)行,redis只會(huì)用大括號(hào)里的 user1 做hash slot計(jì)算,所以算出來的slot值肯定相同,最后都能落在同一slot。
對(duì)比: 哨兵leader選舉流程
當(dāng)一個(gè)master服務(wù)器被某sentinel視為下線狀態(tài)后,該sentinel會(huì)與其他sentinel協(xié)商選出sentinel的leader進(jìn)行故障轉(zhuǎn)移工作。每個(gè)發(fā)現(xiàn)master服務(wù)器進(jìn)入下線的sentinel都可以要求其他sentinel選自己為sentinel的leader,選舉是先到先得。同時(shí)每個(gè)sentinel每次選舉都會(huì)自增配置紀(jì)元(選舉周期),每個(gè)紀(jì)元中只會(huì)選擇一個(gè)sentinel的leader。如果所有超過一半的sentinel選舉某sentinel作為leader。之后該sentinel進(jìn)行故障轉(zhuǎn)移操作,從存活的slave中選舉出新的master,這個(gè)選舉過程跟集群的master選舉很類似。
哨兵集群只有一個(gè)哨兵節(jié)點(diǎn),redis的主從也能正常運(yùn)行以及選舉master,如果master掛了,那唯一的那個(gè)哨兵節(jié)點(diǎn)就是哨兵leader了,可以正常選舉新master。
不過為了高可用一般都推薦至少部署三個(gè)哨兵節(jié)點(diǎn)。為什么推薦奇數(shù)個(gè)哨兵節(jié)點(diǎn)原理跟集群奇數(shù)個(gè)master節(jié)點(diǎn)類似。
集群故障轉(zhuǎn)移
Redis 集群中的節(jié)點(diǎn)分為主節(jié)點(diǎn)(master)和從節(jié)點(diǎn)(slave),其中主節(jié)點(diǎn)用于處理槽,而從節(jié)點(diǎn)則用于復(fù)制某個(gè)主節(jié)點(diǎn),并且在復(fù)制主節(jié)點(diǎn)下線時(shí),代替主節(jié)點(diǎn)繼續(xù)處理命令請(qǐng)求。
故障檢測(cè)
集群中的每個(gè)節(jié)點(diǎn)都會(huì)定期地向集群中的其他節(jié)點(diǎn)發(fā)送PING消息,以此來檢測(cè)對(duì)方是否在線,如果接收PING消息的節(jié)點(diǎn)沒有在規(guī)定的時(shí)間內(nèi),向發(fā)送PING消息的節(jié)點(diǎn)返回PONG消息,那么發(fā)送PING消息的節(jié)點(diǎn)就會(huì)將接收PING消息的節(jié)點(diǎn)標(biāo)記為疑似下線(probable fail,PFAIL)。
如果在一個(gè)集群里面,半數(shù)以上負(fù)責(zé)處理槽的主節(jié)點(diǎn)都將某個(gè)主節(jié)點(diǎn)x報(bào)告為疑似下線,那么這個(gè)主節(jié)點(diǎn)x將被標(biāo)記為已下線(FAIL),將主節(jié)點(diǎn)x標(biāo)記為已下線的節(jié)點(diǎn)會(huì)向集群廣播一條關(guān)于主節(jié)點(diǎn)x的FAIL消息,所有收到這條FAIL消息的節(jié)點(diǎn)都會(huì)立即將主節(jié)點(diǎn)x標(biāo)記為已下線。
舉個(gè)例子,主節(jié)點(diǎn)7002和主節(jié)點(diǎn)7003都認(rèn)為主節(jié)點(diǎn)7000進(jìn)入了下線狀態(tài),并且主節(jié)點(diǎn)7001也認(rèn)為主節(jié)點(diǎn)7000進(jìn)入了疑似下線狀態(tài),在集群四個(gè)負(fù)責(zé)處理槽的主節(jié)點(diǎn)里面,有三個(gè)都將主節(jié)點(diǎn)7000標(biāo)記為下線,數(shù)量已經(jīng)超過了半數(shù),所以主節(jié)點(diǎn)7001會(huì)將主節(jié)點(diǎn)7000標(biāo)記為已下線,并向集群廣播一條關(guān)于主節(jié)點(diǎn)7000的FAIL消息,如圖所示:
故障轉(zhuǎn)移
當(dāng)一個(gè)從節(jié)點(diǎn)發(fā)現(xiàn)自己正在復(fù)制的主節(jié)點(diǎn)進(jìn)入了已下線狀態(tài)時(shí),從節(jié)點(diǎn)將開始對(duì)下線主節(jié)點(diǎn)進(jìn)行故障轉(zhuǎn)移,以下是故障轉(zhuǎn)移的執(zhí)行步驟:
-
復(fù)制下線主節(jié)點(diǎn)的所有從節(jié)點(diǎn)里面,會(huì)有一個(gè)從節(jié)點(diǎn)被選中。
-
被選中的從節(jié)點(diǎn)會(huì)執(zhí)行
SLAVEOF no one
命令,成為新的主節(jié)點(diǎn)。 -
新的主節(jié)點(diǎn)會(huì)撤銷所有對(duì)已下線主節(jié)點(diǎn)的槽指派,并將這些槽全部指派給自己。
-
新的主節(jié)點(diǎn)向集群廣播一條PONG消息,這條PONG消息可以讓集群中的其他節(jié)點(diǎn)立即知道這個(gè)節(jié)點(diǎn)已經(jīng)由從節(jié)點(diǎn)變成了主節(jié)點(diǎn),并且這個(gè)主節(jié)點(diǎn)已經(jīng)接管了原本由已下線節(jié)點(diǎn)負(fù)責(zé)處理的槽。
-
新的主節(jié)點(diǎn)開始接收和自己負(fù)責(zé)處理的槽有關(guān)的命令請(qǐng)求,故障轉(zhuǎn)移完成。
重新選擇新節(jié)點(diǎn)
新的主節(jié)點(diǎn)是通過選舉產(chǎn)生的。以下是集群選舉新的主節(jié)點(diǎn)的方法:
-
集群的配置紀(jì)元是一個(gè)自增計(jì)數(shù)器,它的初始值為0。
-
當(dāng)集群里的某個(gè)節(jié)點(diǎn)開始一次故障轉(zhuǎn)移操作時(shí),集群配置紀(jì)元的值會(huì)被增一。
-
對(duì)于每個(gè)配置紀(jì)元,集群里每個(gè)負(fù)責(zé)處理槽的主節(jié)點(diǎn)都有一次投票的機(jī)會(huì),而第一個(gè)向主節(jié)點(diǎn)要求投票的從節(jié)點(diǎn)將獲得主節(jié)點(diǎn)的投票。
-
當(dāng)從節(jié)點(diǎn)發(fā)現(xiàn)自己正在復(fù)制的主節(jié)點(diǎn)進(jìn)入已下線狀態(tài)時(shí),從節(jié)點(diǎn)會(huì)向集群廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息、并且具有投票權(quán)的主節(jié)點(diǎn)向這個(gè)從節(jié)點(diǎn)投票。
-
如果一個(gè)主節(jié)點(diǎn)具有投票權(quán)(它正在負(fù)責(zé)處理槽),并且這個(gè)主節(jié)點(diǎn)尚未投票給其他從節(jié)點(diǎn),那么主節(jié)點(diǎn)將向要求投票的從節(jié)點(diǎn)返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個(gè)主節(jié)點(diǎn)支持從節(jié)點(diǎn)成為新的主節(jié)點(diǎn)。
-
每個(gè)參與選舉的從節(jié)點(diǎn)都會(huì)接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據(jù)自己收到了多少條這種消息來統(tǒng)計(jì)自己獲得了多少主節(jié)點(diǎn)的支持。
-
如果集群里有N個(gè)具有投票權(quán)的主節(jié)點(diǎn),那么當(dāng)一個(gè)從節(jié)點(diǎn)收集到大于等于N/2+1張支持票時(shí),這個(gè)從節(jié)點(diǎn)就會(huì)當(dāng)選為新的主節(jié)點(diǎn)。
-
因?yàn)樵诿恳粋€(gè)配置紀(jì)元里面,每個(gè)具有投票權(quán)的主節(jié)點(diǎn)只能投一次票,所以如果有N個(gè)主節(jié)點(diǎn)進(jìn)行投票,那么具有大于等于N/2+1張支持票的從節(jié)點(diǎn)只會(huì)有一個(gè),這確保了新的主節(jié)點(diǎn)只會(huì)有一個(gè)。
-
如果在一個(gè)配置紀(jì)元里面沒有從節(jié)點(diǎn)能收集到足夠多的支持票,那么集群進(jìn)入一個(gè)新的配置紀(jì)元,并再次進(jìn)行選舉,直到選出新的主節(jié)點(diǎn)為止。
Cluster 選舉新主節(jié)點(diǎn)的方法和 Sentinel 的方法非常相似,因?yàn)閮烧叨际腔?Raft 算法的領(lǐng)頭選舉(leader election)方法來實(shí)現(xiàn)的。
水平拓展
拓展主節(jié)點(diǎn)
集群狀態(tài)查詢
127.0.0.1:7000> cluster nodes
6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004@17004 slave de631f8ac9649f5d9fb12013dc01407f953c3299 0 1653897401000 3 connected
de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002@17002 master - 0 1653897402475 3 connected 10923-16383
a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003@17003 slave e36fc81472afb04b9c88af1504a8e02647de1b13 0 1653897401956 2 connected
e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001@17001 master - 0 1653897401541 2 connected 5461-10922
0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000@17000 myself,master - 0 1653897400000 1 connected 0-5460
57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005@17005 slave 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 0 1653897401000 1 connected
127.0.0.1:7000>
拓展規(guī)劃
我們?cè)谠技夯A(chǔ)上再增加一主(7006)一從(7007)
IP | 端口 | 配置文件 | 類型 |
---|---|---|---|
127.0.0.1 | 7000 | 7000/redis-7000.conf | master |
127.0.0.1 | 7001 | 7001/redis-7001.conf | master |
127.0.0.1 | 7002 | 7002/redis-7002.conf | master |
127.0.0.1 | 7003 | 7003/redis-7003.conf | slave |
127.0.0.1 | 7004 | 7004/redis-7004.conf | slave |
127.0.0.1 | 7005 | 7005/redis-7005.conf | slave |
127.0.0.1 | 7006 | 7006/redis-7006.conf | master |
127.0.0.1 | 7007 | 7007/redis-7007.conf | slave |
啟動(dòng)節(jié)點(diǎn)
redis-server redis-7006.conf
redis-server redis-7007.conf
集群中加入節(jié)點(diǎn)
加入集群
? etc redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7001 >>> Adding node 127.0.0.1:7006 to cluster 127.0.0.1:7001 >>> Performing Cluster Check (using node 127.0.0.1:7001) M: e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003 slots: (0 slots) slave replicates e36fc81472afb04b9c88af1504a8e02647de1b13 S: 6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004 slots: (0 slots) slave replicates de631f8ac9649f5d9fb12013dc01407f953c3299 S: 57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005 slots: (0 slots) slave replicates 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 M: de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000 slots:[0-5460] (5461 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. >>> Send CLUSTER MEET to node 127.0.0.1:7006 to make it join the cluster. [OK] New node added correctly. ? etc
如下,7006加入了集群,并且默認(rèn)是一個(gè)master節(jié)點(diǎn):
? etc redis-cli -p 7001 cluster nodes
2109c2832177e8514174c6ef8fefd681076e28df 127.0.0.1:7006@17006 master - 0 1653919429534 0 connected
a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003@17003 slave e36fc81472afb04b9c88af1504a8e02647de1b13 0 1653919430000 2 connected
6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004@17004 slave de631f8ac9649f5d9fb12013dc01407f953c3299 0 1653919430549 3 connected
57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005@17005 slave 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 0 1653919430549 1 connected
de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002@17002 master - 0 1653919429129 3 connected 10923-16383
0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000@17000 master - 0 1653919430549 1 connected 0-5460
e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001@17001 myself,master - 0 1653919429000 2 connected 5461-10922
設(shè)置和遷移分片
為集群分配分片
redis-cli --cluster reshard 127.0.0.1:7001
在執(zhí)行過程中會(huì)詢問,計(jì)劃遷移槽數(shù),遷移數(shù)據(jù)目標(biāo),以及遷移數(shù)據(jù)來源。
重新分配后的結(jié)果查詢 redis-cli -p 7001 cluster nodes
配置從 7006 的從節(jié)點(diǎn) 7007, 同樣也是先執(zhí)行加入集群的命令
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
設(shè)置從節(jié)點(diǎn)
我們需要執(zhí)行 replicate 命令來指定當(dāng)前節(jié)點(diǎn)(從節(jié)點(diǎn))的主節(jié)點(diǎn)id為哪個(gè),首先需要連接新加的7007節(jié)點(diǎn)的客戶端,然后使用集群命令進(jìn)行操作,把當(dāng)前的7007(slave)節(jié)點(diǎn)指定到一個(gè)主節(jié)點(diǎn)下(這里使用之前創(chuàng)建的7006主節(jié)點(diǎn))
具體的命令如下:
# 查詢集群節(jié)點(diǎn)
? etc redis-cli -p 7001 cluster nodes
2109c2832177e8514174c6ef8fefd681076e28df 127.0.0.1:7006@17006 master - 0 1653920562718 7 connected 0-1332 5461-6794 10923-12255
a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003@17003 slave e36fc81472afb04b9c88af1504a8e02647de1b13 0 1653920561710 2 connected
6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004@17004 slave de631f8ac9649f5d9fb12013dc01407f953c3299 0 1653920562000 3 connected
57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005@17005 slave 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 0 1653920562516 1 connected
8d935918d877a63283e1f3a1b220cdc8cb73c414 127.0.0.1:7007@17007 master - 0 1653920563000 0 connected
de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002@17002 master - 0 1653920563000 3 connected 12256-16383
0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000@17000 master - 0 1653920561506 1 connected 1333-5460
e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001@17001 myself,master - 0 1653920562000 2 connected 6795-10922
# 登錄到新加節(jié)點(diǎn)
? etc redis-cli -p 7007
127.0.0.1:7007>
# 制定當(dāng)前當(dāng)前節(jié)點(diǎn)的主節(jié)點(diǎn) 7006
127.0.0.1:7007> cluster replicate 2109c2832177e8514174c6ef8fefd681076e28df
OK
# 重新查詢集群狀態(tài)
127.0.0.1:7007> cluster nodes
57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005@17005 slave 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 0 1653920688403 1 connected
8d935918d877a63283e1f3a1b220cdc8cb73c414 127.0.0.1:7007@17007 myself,slave 2109c2832177e8514174c6ef8fefd681076e28df 0 1653920685000 7 connected
2109c2832177e8514174c6ef8fefd681076e28df 127.0.0.1:7006@17006 master - 0 1653920688000 7 connected 0-1332 5461-6794 10923-12255
de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002@17002 master - 0 1653920687000 3 connected 12256-16383
0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000@17000 master - 0 1653920687000 1 connected 1333-5460
6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004@17004 slave de631f8ac9649f5d9fb12013dc01407f953c3299 0 1653920688099 3 connected
a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003@17003 slave e36fc81472afb04b9c88af1504a8e02647de1b13 0 1653920688504 2 connected
e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001@17001 master - 0 1653920687392 2 connected 6795-10922
最后我們?cè)俅尾樵?,發(fā)現(xiàn) 7007 成功變成了 7008 的主節(jié)點(diǎn)
主節(jié)點(diǎn)下線
徹底刪除主節(jié)點(diǎn),因?yàn)橹鞴?jié)點(diǎn)中存在數(shù)據(jù),所以我們可以分為兩個(gè)步驟操作
-
數(shù)據(jù)遷移
-
節(jié)點(diǎn)下線
為了方便驗(yàn)證,我先設(shè)置一個(gè)數(shù)據(jù)
127.0.0.1:7001> set sdl 123 -> Redirected to slot [11164] located at 127.0.0.1:7006 OK 127.0.0.1:7006> get sdl "123"
先下先從節(jié)點(diǎn), 執(zhí)行一下命令:
redis-cli --cluster del-node 127.0.0.1:7007 8d935918d877a63283e1f3a1b220cdc8cb73c414
數(shù)據(jù)遷出
? etc redis-cli --cluster reshard 127.0.0.1:7001 >>> Performing Cluster Check (using node 127.0.0.1:7001) M: e36fc81472afb04b9c88af1504a8e02647de1b13 127.0.0.1:7001 slots:[10212-10922] (711 slots) master 1 additional replica(s) M: 2109c2832177e8514174c6ef8fefd681076e28df 127.0.0.1:7006 slots:[11471-12255] (785 slots) master S: a89dcff90147f6cc9425ff0c6e4bead7017dc1e1 127.0.0.1:7003 slots: (0 slots) slave replicates e36fc81472afb04b9c88af1504a8e02647de1b13 S: 6ac6065d07eb44b674a181da897401ec4cea9571 127.0.0.1:7004 slots: (0 slots) slave replicates de631f8ac9649f5d9fb12013dc01407f953c3299 S: 57ddd19f38d8b748386944d15d32671fb5cb1570 127.0.0.1:7005 slots: (0 slots) slave replicates 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 M: de631f8ac9649f5d9fb12013dc01407f953c3299 127.0.0.1:7002 slots:[15624-16383] (760 slots) master 1 additional replica(s) M: 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 127.0.0.1:7000 slots:[0-10211],[10923-11470],[12256-15623] (14128 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 785 What is the receiving node ID? 0acfc8b3dd2223333a03bbcf856dd2a839d2e072 Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: 2109c2832177e8514174c6ef8fefd681076e28df Source node #2: done
查詢節(jié)點(diǎn)槽信息
節(jié)點(diǎn)下線
redis-cli --cluster del-node 127.0.0.1:7006 2109c2832177e8514174c6ef8fefd681076e28df
執(zhí)行后結(jié)果如下:
參考資料
-
《Redis 設(shè)計(jì)與實(shí)現(xiàn)》黃健宏
-
Redis 為什么是 16384 個(gè)槽 ?
-
https://blog.csdn.net/wanderstarrysky/article/details/118157751
-
https://segmentfault.com/a/1190000038373546
作者:鄭哥 運(yùn)維開發(fā)故事
歡迎關(guān)注:運(yùn)維開發(fā)故事