啟動(dòng)節(jié)點(diǎn)
Redis服務(wù)器在啟動(dòng)時(shí)锦亦,會(huì)根據(jù)cluster-enabled配置決定是否開(kāi)啟服務(wù)器的集群模式忧侧。如果未開(kāi)啟博其,就進(jìn)入stand alone模式尝盼,以普通單機(jī)Redis方式運(yùn)行模蜡。否則進(jìn)入集群模式漠趁。
一個(gè)Redis集群由多個(gè)節(jié)點(diǎn)(node)組成。初始化時(shí)哩牍,每個(gè)node都是相互獨(dú)立的棚潦,它們都處于一個(gè)只包含自己的集群當(dāng)中。要想組成一個(gè)真正可用的集群膝昆,必須將多個(gè)獨(dú)立的節(jié)點(diǎn)連接起來(lái)丸边。
向一個(gè)node發(fā)送CLUSTER MEET命令叠必,可以讓node與指定的節(jié)點(diǎn)進(jìn)行握手,握手成功后妹窖,指定的節(jié)點(diǎn)就加入到node所在的集群中纬朝。
新的節(jié)點(diǎn)加入集群后。node會(huì)通過(guò)Gossip協(xié)議傳播給集群中的其他節(jié)點(diǎn)骄呼,讓其他節(jié)點(diǎn)也與新加入的節(jié)點(diǎn)握手共苛。最終經(jīng)過(guò)一段時(shí)間后,集群中的所有節(jié)點(diǎn)就建立起了連接蜓萄。
實(shí)現(xiàn)數(shù)據(jù)分區(qū)
槽(slot)概念:
Redis Cluster中有一個(gè)16384長(zhǎng)度的虛擬的槽的概念隅茎,他們并不是真正存在的,他們的編號(hào)為0嫉沽、1辟犀、2、3……16382绸硕、16383堂竟。Redis Cluster中的每個(gè)Master節(jié)點(diǎn)都會(huì)負(fù)責(zé)一部分的槽,當(dāng)有某個(gè)key被映射到某個(gè)Master負(fù)責(zé)的槽玻佩,那么這個(gè)Master負(fù)責(zé)為這個(gè)key提供服務(wù)出嘹,至于哪個(gè)Master節(jié)點(diǎn)負(fù)責(zé)哪個(gè)槽,這是可以由用戶指定的咬崔,也可以在初始化的時(shí)候自動(dòng)生成(redis-trib.rb腳本)税稼。在Redis Cluster中,只有Master才擁有槽的所有權(quán)刁赦,如果是某個(gè)Master的slave娶聘,這個(gè)slave只負(fù)責(zé)槽的使用,但是沒(méi)有所有權(quán)甚脉;集群使用公式CRC16(key) % 16384來(lái)計(jì)算鍵key屬于哪個(gè)槽,其中CRC16(key)語(yǔ)句用于計(jì)算鍵key的CRC16校驗(yàn)和铆农。
節(jié)點(diǎn)的槽指派信息:
clusterNode結(jié)構(gòu)的slots屬性和numslot屬性記錄了節(jié)點(diǎn)負(fù)責(zé)處理那些槽
struct clusterNode {
unsignedchar slots[16384/8];? // 二進(jìn)制位數(shù)組(bit array)牺氨,這個(gè)數(shù)組的長(zhǎng)度為16384/8=2048個(gè)字節(jié),共包含16384個(gè)二進(jìn)制位
int numslots;//節(jié)點(diǎn)負(fù)責(zé)處理的槽的數(shù)量墩剖,即是slots數(shù)組中值為1的二進(jìn)制位的數(shù)量
};
Master節(jié)點(diǎn)用bit來(lái)標(biāo)識(shí)對(duì)于某個(gè)槽自己是否擁有猴凹。比如對(duì)于編號(hào)為1的槽,Master只要判斷序列的第二位(索引從0開(kāi)始)是不是為1即可岭皂。時(shí)間復(fù)雜度為O(1)郊霎。
傳播節(jié)點(diǎn)的槽指派信息(struct clusterNode)
一個(gè)節(jié)點(diǎn)會(huì)將自己的slots數(shù)組通過(guò)消息發(fā)送給集群中的其他節(jié)點(diǎn),告知其他節(jié)點(diǎn)自己 目前負(fù)責(zé)處理哪些槽
客戶端的api爷绘,可以對(duì)指定的數(shù)據(jù)书劝,讓他們走同一個(gè)hash slot进倍,通過(guò)hash tag來(lái)實(shí)現(xiàn)
redis cluster哈希槽數(shù)量不能改變,因?yàn)榇a算法寫死了购对,固定是2的14次方這個(gè)數(shù)字上16384
摘自Redis官網(wǎng)的Data type章節(jié)猾昆,內(nèi)存允許的情況下,一個(gè)hash slot可以存超過(guò)40億數(shù)據(jù)
一個(gè)hash slot中會(huì)有很多key和value骡苞。你可以理解成表的分區(qū)垂蜗。使用單節(jié)點(diǎn)時(shí)的redis時(shí)只有一個(gè)表,所有的key都放在這個(gè)表里解幽;2.改用Redis Cluster以后會(huì)自動(dòng)為你生成16384個(gè)分區(qū)表贴见。
集群節(jié)點(diǎn)屬性
集群中每個(gè)Master node負(fù)責(zé)存儲(chǔ)數(shù)據(jù)、集群狀態(tài)躲株,包括slots與nodes對(duì)應(yīng)關(guān)系片部。Master nodes能夠自動(dòng)發(fā)現(xiàn)其他nodes,檢測(cè)failure節(jié)點(diǎn)徘溢,當(dāng)某個(gè)Master節(jié)點(diǎn)失效時(shí)吞琐,集群能將核實(shí)的Slave提升為Master。節(jié)點(diǎn)定時(shí)會(huì)將這些信息發(fā)送給其他節(jié)點(diǎn)然爆,cluster nodes命令輸出是空格分割的CSV字符串站粟,每行代表集群中的一個(gè)節(jié)點(diǎn)
67ed2db8d677e59ec4a4cefb06858cf2a1a89fa1 127.0.0.1:30002 master - 0 1426238316232 2 connected 5461-10922
每行的組成結(jié)構(gòu)如下:
<id> <ip:port> <flags> <master> <ping-sent> <pong-recv> <config-epoch> <link-state> <slot> <slot> ... <slot>
每項(xiàng)的含義如下:
id: 節(jié)點(diǎn)ID,是一個(gè)40字節(jié)的隨機(jī)字符串,這個(gè)值在節(jié)點(diǎn)啟動(dòng)的時(shí)候創(chuàng)建曾雕,并且永遠(yuǎn)不會(huì)改變(除非使用CLUSTER RESET HARD命令)奴烙。
ip:port: 客戶端與節(jié)點(diǎn)通信使用的地址.
flags: 逗號(hào)分割的標(biāo)記位,可能的值有: myself, master, slave, fail?, fail, handshake, noaddr, noflags. 下一部分將詳細(xì)介紹這些標(biāo)記.
master: 如果節(jié)點(diǎn)是slave剖张,并且已知master節(jié)點(diǎn)切诀,則這里列出master節(jié)點(diǎn)ID,否則的話這里列出”-“。
ping-sent: 最近一次發(fā)送ping的時(shí)間搔弄,這個(gè)時(shí)間是一個(gè)unix毫秒時(shí)間戳幅虑,0代表沒(méi)有發(fā)送過(guò).
pong-recv: 最近一次收到pong的時(shí)間,使用unix時(shí)間戳表示.
config-epoch: 節(jié)點(diǎn)的epoch值(or of the current master if the node is a slave)顾犹。每當(dāng)節(jié)點(diǎn)發(fā)生失敗切換時(shí)倒庵,都會(huì)創(chuàng)建一個(gè)新的,獨(dú)特的炫刷,遞增的epoch擎宝。如果多個(gè)節(jié)點(diǎn)競(jìng)爭(zhēng)同一個(gè)哈希槽時(shí),epoch值更高的節(jié)點(diǎn)會(huì)搶奪到浑玛。
link-state: node-to-node集群總線使用的鏈接的狀態(tài)绍申,我們使用這個(gè)鏈接與集群中其他節(jié)點(diǎn)進(jìn)行通信.值可以是 connected 和 disconnected.
slot: 哈希槽值或者一個(gè)哈希槽范圍. 從第9個(gè)參數(shù)開(kāi)始,后面最多可能有16384個(gè) 數(shù)(limit never reached)。代表當(dāng)前節(jié)點(diǎn)可以提供服務(wù)的所有哈希槽值极阅。如果只是一個(gè)值,那就是只有一個(gè)槽會(huì)被使用胃碾。如果是一個(gè)范圍,這個(gè)值表示為起始槽-結(jié)束槽涂屁,節(jié)點(diǎn)將處理包括起始槽和結(jié)束槽在內(nèi)的所有哈希槽书在。
各flags的含義 (上面所說(shuō)數(shù)據(jù)項(xiàng)3):
myself: 當(dāng)前連接的節(jié)點(diǎn).
master: 節(jié)點(diǎn)是master.
slave: 節(jié)點(diǎn)是slave.
fail?: 節(jié)點(diǎn)處于PFAIL 狀態(tài)。 當(dāng)前節(jié)點(diǎn)無(wú)法聯(lián)系拆又,但邏輯上是可達(dá)的 (非 FAIL 狀態(tài)).
fail: 節(jié)點(diǎn)處于FAIL 狀態(tài). 大部分(一半以上)節(jié)點(diǎn)都無(wú)法與其取得聯(lián)系將會(huì)將改節(jié)點(diǎn)由 PFAIL 狀態(tài)升級(jí)至FAIL狀態(tài)儒旬。
handshake: 還未取得信任的節(jié)點(diǎn),當(dāng)前正在與其進(jìn)行握手.
noaddr: 沒(méi)有地址的節(jié)點(diǎn)(No address known for this node).
noflags: 連個(gè)標(biāo)記都沒(méi)有(No flags at all)
紀(jì)元(epoch)概念作用
用來(lái)給事件增加版本號(hào)帖族。Redis 集群中的紀(jì)元主要是兩種:currentEpoch 和 configEpoch栈源。
currentEpoch
這是一個(gè)集群狀態(tài)相關(guān)的概念,可以當(dāng)做記錄集群狀態(tài)變更的遞增版本號(hào)竖般。每個(gè)集群節(jié)點(diǎn)甚垦,都會(huì)通過(guò) server.cluster->currentEpoch 記錄當(dāng)前的 currentEpoch,集群節(jié)點(diǎn)創(chuàng)建時(shí)涣雕,不管是 master 還是 slave艰亮,都置 currentEpoch 為 0,當(dāng)前節(jié)點(diǎn)接收到來(lái)自其他節(jié)點(diǎn)的包時(shí)挣郭,如果發(fā)送者的 currentEpoch(消息頭部會(huì)包含發(fā)送者的 currentEpoch)大于當(dāng)前節(jié)點(diǎn)的currentEpoch迄埃,那么當(dāng)前節(jié)點(diǎn)會(huì)更新 currentEpoch 為發(fā)送者的 currentEpoch。因此兑障,集群中所有節(jié)點(diǎn)的 currentEpoch 最終會(huì)達(dá)成一致侄非,相當(dāng)于對(duì)集群狀態(tài)的認(rèn)知達(dá)成了一致。
currentEpoch 作用
當(dāng)集群的狀態(tài)發(fā)生改變流译,某個(gè)節(jié)點(diǎn)為了執(zhí)行一些動(dòng)作需要尋求其他節(jié)點(diǎn)的同意時(shí)逞怨,就會(huì)增加 currentEpoch 的值。目前 currentEpoch 只用于 slave 的故障轉(zhuǎn)移流程,當(dāng) slave A 發(fā)現(xiàn)其所屬的 master 下線時(shí),就會(huì)試圖發(fā)起故障轉(zhuǎn)移流程。首先就是增加 currentEpoch 的值,這個(gè)增加后的 currentEpoch 是所有集群節(jié)點(diǎn)中最大的吨岭。然后slave A 向所有節(jié)點(diǎn)發(fā)起拉票請(qǐng)求,請(qǐng)求其他 master 投票給自己,使自己能成為新的 master项贺。其他節(jié)點(diǎn)收到包后,發(fā)現(xiàn)發(fā)送者的 currentEpoch 比自己的 currentEpoch 大寇蚊,就會(huì)更新自己的 currentEpoch笔时,并在尚未投票的情況下,投票給 slave A仗岸,表示同意使其成為新的 master允耿。
configEpoch
這是一個(gè)集群節(jié)點(diǎn)配置相關(guān)的概念借笙,每個(gè)集群節(jié)點(diǎn)都有自己獨(dú)一無(wú)二的 configepoch。所謂的節(jié)點(diǎn)配置较锡,實(shí)際上是指節(jié)點(diǎn)所負(fù)責(zé)的槽位信息业稼。
每一個(gè) master 在向其他節(jié)點(diǎn)發(fā)送包時(shí),都會(huì)附帶其 configEpoch 信息蚂蕴,以及一份表示它所負(fù)責(zé)的 slots 信息低散。而 slave 向其他節(jié)點(diǎn)發(fā)送包時(shí),其包中的 configEpoch 和負(fù)責(zé)槽位信息骡楼,是其 master 的 configEpoch 和負(fù)責(zé)的 slot 信息熔号。節(jié)點(diǎn)收到包之后,就會(huì)根據(jù)包中的 configEpoch 和負(fù)責(zé)的 slots 信息鸟整,記錄到相應(yīng)節(jié)點(diǎn)屬性中引镊。
configEpoch 作用
configEpoch 主要用于解決不同的節(jié)點(diǎn)的配置發(fā)生沖突的情況。舉個(gè)例子就明白了:節(jié)點(diǎn)A 宣稱負(fù)責(zé) slot 1篮条,其向外發(fā)送的包中弟头,包含了自己的 configEpoch 和負(fù)責(zé)的 slots 信息。節(jié)點(diǎn) C 收到 A 發(fā)來(lái)的包后涉茧,發(fā)現(xiàn)自己當(dāng)前沒(méi)有記錄 slot 1 的負(fù)責(zé)節(jié)點(diǎn)(也就是 server.cluster->slots[1] 為 NULL)赴恨,就會(huì)將 A 置為 slot 1 的負(fù)責(zé)節(jié)點(diǎn)(server.cluster->slots[1] = A),并記錄節(jié)點(diǎn) A 的 configEpoch降瞳。后來(lái)嘱支,節(jié)點(diǎn) C 又收到了 B 發(fā)來(lái)的包,它也宣稱負(fù)責(zé) slot 1挣饥,此時(shí)除师,如何判斷 slot 1 到底由誰(shuí)負(fù)責(zé)呢?
這就是 configEpoch 起作用的時(shí)候了扔枫,C 在 B 發(fā)來(lái)的包中汛聚,發(fā)現(xiàn)它的 configEpoch,要比 A 的大短荐,說(shuō)明 B 是更新的配置倚舀。因此,就將 slot 1 的負(fù)責(zé)節(jié)點(diǎn)設(shè)置為 B(server.cluster->slots[1] = B)忍宋。在 slave 發(fā)起選舉痕貌,獲得足夠多的選票之后,成功當(dāng)選時(shí)糠排,也就是 slave 試圖替代其已經(jīng)下線的舊 master舵稠,成為新的 master 時(shí),會(huì)增加它自己的 configEpoch,使其成為當(dāng)前所有集群節(jié)點(diǎn)的 configEpoch 中的最大值哺徊。這樣室琢,該 slave 成為 master 后,就會(huì)向所有節(jié)點(diǎn)發(fā)送廣播包落追,強(qiáng)制其他節(jié)點(diǎn)更新相關(guān) slots 的負(fù)責(zé)節(jié)點(diǎn)為自己盈滴。
Epoch Collision
2):每個(gè)節(jié)點(diǎn)的node epoch都是獨(dú)一無(wú)二的;
3):擁有越高epoch的節(jié)點(diǎn), 集群信息越新轿钠;
實(shí)際上, 在遷移slot或者使用cluster failover的時(shí)候, 如果多個(gè)節(jié)點(diǎn)同時(shí)bump epoch, 就有可能出現(xiàn)多個(gè)節(jié)點(diǎn)擁有同一個(gè)epoch, 違反上述原則(2)和(3). 這個(gè)時(shí)候擁有較小node id的節(jié)點(diǎn)就會(huì)自動(dòng)再一次bump epoch, 以保證原則(3). 而原則(2)實(shí)際上因此也并不嚴(yán)格成立, 因?yàn)榻鉀Qepoch collision需要一小段時(shí)間巢钓。
集群通信
集群消息通信通過(guò)集群總線通信,集群總線端口為客戶端服務(wù)端口+10000(10000是固定值)
最開(kāi)始谣膳,每個(gè)Redis實(shí)例自己是一個(gè)集群竿报,我們通過(guò)cluster meet讓各個(gè)結(jié)點(diǎn)互相“握手”。Redis Cluster目前缺少結(jié)點(diǎn)的自動(dòng)發(fā)現(xiàn)功能继谚。連接各個(gè)節(jié)點(diǎn)的工作使用CLUSTER MEET命令來(lái)完成烈菌,在一個(gè)實(shí)例上。
CLUSTER MEET <ip> <port>
CLUSTER MEET命令實(shí)現(xiàn):
1)節(jié)點(diǎn)A會(huì)為節(jié)點(diǎn)B創(chuàng)建一個(gè)clusterNode結(jié)構(gòu)花履,并將該結(jié)構(gòu)添加到自己的clusterState.nodes字典里面芽世。
2)節(jié)點(diǎn)A根據(jù)CLUSTER MEET命令給定的IP地址和端口號(hào),向節(jié)點(diǎn)B發(fā)送一條MEET消息诡壁。
3)節(jié)點(diǎn)B接收到節(jié)點(diǎn)A發(fā)送的MEET消息济瓢,節(jié)點(diǎn)B會(huì)為節(jié)點(diǎn)A創(chuàng)建一個(gè)clusterNode結(jié)構(gòu),并將該結(jié)構(gòu)添加到自己的clusterState.nodes字典里面妹卿。
4)節(jié)點(diǎn)B向節(jié)點(diǎn)A返回一條PONG消息旺矾。
5)節(jié)點(diǎn)A收到節(jié)點(diǎn)B返回的PONG消息,通過(guò)這條PONG消息節(jié)點(diǎn)A可以知道節(jié)點(diǎn)B已經(jīng)成功的接收了自己發(fā)送的MEET消息夺克。
6)之后箕宙,節(jié)點(diǎn)A將向節(jié)點(diǎn)B返回一條PING消息。
7)節(jié)點(diǎn)B將收到的節(jié)點(diǎn)A返回的PING消息铺纽,通過(guò)這條PING消息節(jié)點(diǎn)B可以知道節(jié)點(diǎn)A已經(jīng)成功的接收到了自己返回的PONG消息柬帕,握手完成。
8)之后狡门,節(jié)點(diǎn)A會(huì)將節(jié)點(diǎn)B的信息通過(guò)Gossip協(xié)議傳播給集群中的其他節(jié)點(diǎn)陷寝,讓其他節(jié)點(diǎn)也與節(jié)點(diǎn)B進(jìn)行握手,最終其馏,經(jīng)過(guò)一段時(shí)間后凤跑,節(jié)點(diǎn)B會(huì)被集群中的所有節(jié)點(diǎn)認(rèn)識(shí)。
集群消息處理clusterProcessPacket
(cluster監(jiān)聽(tīng)cluster端口叛复,并通過(guò)clusterAcceptHandler接受集群節(jié)點(diǎn)發(fā)起的連接請(qǐng)求饶火,通過(guò)aeCreateFileEvent將clusterReadHandler注冊(cè)進(jìn)事件回調(diào)鹏控,讀取node發(fā)送的數(shù)據(jù)包。clusterReadHandler讀取到完整的數(shù)據(jù)包后肤寝,調(diào)用clusterProcessPacket處理包請(qǐng)求)
1)更新接收消息計(jì)數(shù)器
2)查找發(fā)送者節(jié)點(diǎn)并且不是handshake(握手)節(jié)點(diǎn)
3)更新自己的epoch和slave的offset信息
4)處理MEET消息,使加入集群
5)從goosip中發(fā)現(xiàn)未知節(jié)點(diǎn)抖僵,發(fā)起handshake(握手)
6)對(duì)PING鲤看,MEET回復(fù)PONG
7)根據(jù)收到的心跳信息更新自己clusterState中的master-slave,slots信息
8)對(duì)FAILOVER_AUTH_REQUEST消息耍群,檢查并投票
9)處理FAIL义桂,F(xiàn)AILOVER_AUTH_ACK,UPDATE信息
定時(shí)任務(wù)clusterCron
1)對(duì)handshake節(jié)點(diǎn)建立Link蹈垢,發(fā)送Ping或Meet
2)向隨機(jī)幾點(diǎn)發(fā)送Ping(每個(gè)節(jié)點(diǎn)維護(hù)定時(shí)任務(wù)默認(rèn)每秒執(zhí)行10次慷吊,每秒會(huì)隨機(jī)選取5個(gè)節(jié)點(diǎn)找出最久沒(méi)有通信的節(jié)點(diǎn)發(fā)送ping消息,用于保證Gossip信息交換的隨機(jī)性曹抬。每100毫秒都會(huì)掃描本地節(jié)點(diǎn)列表溉瓶,如果發(fā)現(xiàn)節(jié)點(diǎn)最近一次接受pong消息的時(shí)間大于cluster_node_timeout/2,則立刻發(fā)送ping消息谤民,防止該節(jié)點(diǎn)信息太長(zhǎng)時(shí)間未更新)
3)如果是從查看是否需要做Failover
4)統(tǒng)計(jì)并決定是否進(jìn)行slave的遷移堰酿,來(lái)平衡不同master的slave數(shù)
5)判斷所有pfail報(bào)告數(shù)是否過(guò)半數(shù)
心跳數(shù)據(jù)(Gossip協(xié)議)
發(fā)送消息頭信息Header
1)所負(fù)責(zé)slots的信息
2)主從信息
3)ip port信息
4)狀態(tài)信息
發(fā)送其他節(jié)點(diǎn)Gossip信息
1)ping_sent, pong_received
2)ip, port信息
3)狀態(tài)信息,比如發(fā)送者認(rèn)為該節(jié)點(diǎn)已經(jīng)不可達(dá)张足,會(huì)在狀態(tài)信息中標(biāo)記其為PFAIL或FAIL
clusterMsg結(jié)構(gòu)的currentEpoch触创、sender、myslots等屬性記錄了發(fā)送者自身的節(jié)點(diǎn)信息为牍,接收者會(huì)根據(jù)這些信息哼绑,在自己的clusterState.nodes字典里找到發(fā)送者對(duì)應(yīng)的clusterNode結(jié)構(gòu),并對(duì)結(jié)構(gòu)進(jìn)行更新碉咆。
Redis集群中的各個(gè)節(jié)點(diǎn)通過(guò)Gossip協(xié)議來(lái)交換各自關(guān)于不同節(jié)點(diǎn)的狀態(tài)信息抖韩,其中Gossip協(xié)議由MEET、PING吟逝、PONG三種消息實(shí)現(xiàn)帽蝶,這三種消息的正文都由兩個(gè)clusterMsgDataGossip結(jié)構(gòu)組成。
每次發(fā)送MEET块攒、PING励稳、PONG消息時(shí),發(fā)送者都從自己的已知節(jié)點(diǎn)列表中隨機(jī)選出兩個(gè)節(jié)點(diǎn)(可以是主節(jié)點(diǎn)或者從節(jié)點(diǎn)),并將這兩個(gè)被選中節(jié)點(diǎn)的信息分別保存到兩個(gè)結(jié)構(gòu)中囱井。
當(dāng)接收者收到消息時(shí)驹尼,接收者會(huì)訪問(wèn)消息正文中的兩個(gè)結(jié)構(gòu),并根據(jù)自己是否認(rèn)識(shí)clusterMsgDataGossip結(jié)構(gòu)中記錄的被選中節(jié)點(diǎn)進(jìn)行操作:
1.如果被選中節(jié)點(diǎn)不存在于接收者的已知節(jié)點(diǎn)列表庞呕,那么說(shuō)明接收者是第一次接觸到被選中節(jié)點(diǎn)新翎,接收者將根據(jù)結(jié)構(gòu)中記錄的IP地址和端口號(hào)等信息程帕,與被選擇節(jié)點(diǎn)進(jìn)行握手。
2.如果被選中節(jié)點(diǎn)已經(jīng)存在于接收者的已知節(jié)點(diǎn)列表地啰,那么說(shuō)明接收者之前已經(jīng)與被選中節(jié)點(diǎn)進(jìn)行過(guò)接觸愁拭,接收者將根據(jù)clusterMsgDataGossip結(jié)構(gòu)記錄的信息,對(duì)被選中節(jié)點(diǎn)對(duì)應(yīng)的clusterNode結(jié)構(gòu)進(jìn)行更新亏吝。
發(fā)送消息數(shù)據(jù)結(jié)構(gòu)
clusterNode結(jié)構(gòu)保存了一個(gè)節(jié)點(diǎn)的當(dāng)前狀態(tài)岭埠,主要字段
1)slots:位圖,由當(dāng)前clusterNode負(fù)責(zé)的slot為1
2)salve, slaveof:主從關(guān)系信息
3)ping_sent, pong_received:心跳包收發(fā)時(shí)間
4)clusterLink *link:節(jié)點(diǎn)間的連接
5)list *fail_reports:收到的節(jié)點(diǎn)不可達(dá)投票
clusterState結(jié)構(gòu)記錄了在當(dāng)前節(jié)點(diǎn)的集群目前所處的狀態(tài)蔚鸥,主要字段
1)myself:指針指向自己的clusterNode
2)currentEpoch:當(dāng)前節(jié)點(diǎn)的最大epoch惜论,可能在心跳包的處理中更新
3)nodes:當(dāng)前節(jié)點(diǎn)記錄的所有節(jié)點(diǎn),為clusterNode指針數(shù)組
4)slots:slot與clusterNode指針映射關(guān)系
5)migrating_slots_to止喷,importing_slots_from:記錄slots的遷移信息
6)failover_auth_time,failover_auth_count,failover_auth_sent,failover_auth_rank,failover_auth_epoch:Failover相關(guān)信息
--clusterNode結(jié)構(gòu)
--clusterState結(jié)構(gòu)
--clusterLink結(jié)構(gòu)
故障發(fā)現(xiàn)
故障恢復(fù)類型
1)集群中一個(gè)Master節(jié)點(diǎn)故障之后馆类,我們讓該Master節(jié)點(diǎn)的子節(jié)點(diǎn)代替該Master節(jié)點(diǎn)繼續(xù)向外提供服務(wù)。這個(gè)步驟我們叫slave promotion弹谁。
2)集群中一個(gè)Master節(jié)點(diǎn)沒(méi)有Slave節(jié)點(diǎn)時(shí)(Orphaned Master)乾巧,我們從其他有富余子節(jié)點(diǎn)的地方遷移過(guò)來(lái)一個(gè)子節(jié)點(diǎn)給這個(gè)孤立節(jié)點(diǎn)。
結(jié)點(diǎn)狀態(tài)
節(jié)點(diǎn)狀態(tài):在線狀態(tài)僵闯、PFAIL(疑似下線狀態(tài))卧抗、FAIL(已下線狀態(tài))
PFAIL:Redis的每個(gè)節(jié)點(diǎn)會(huì)不停的向其他節(jié)點(diǎn)發(fā)送PING消息來(lái)與其他節(jié)點(diǎn)同步信息的同時(shí)檢測(cè)其他節(jié)點(diǎn)是否可達(dá)。我們以節(jié)點(diǎn)1的視角為例去介紹這個(gè)過(guò)程鳖粟。當(dāng)節(jié)點(diǎn)1與節(jié)點(diǎn)3建立連接之后社裆,會(huì)不定時(shí)的向節(jié)點(diǎn)3發(fā)送PING命令,每次發(fā)送PING命令時(shí)節(jié)點(diǎn)1會(huì)記錄發(fā)送命令的時(shí)刻ping_sent向图,同時(shí)會(huì)至多等待node_timeout/2來(lái)獲得節(jié)點(diǎn)3的回復(fù)泳秀。正常情況下,節(jié)點(diǎn)3接收到PING命令后會(huì)給節(jié)點(diǎn)1返回PONG回復(fù)榄攀,但是此時(shí)節(jié)點(diǎn)3故障了嗜傅,那么節(jié)點(diǎn)1會(huì)發(fā)現(xiàn)在等待node_timeout/2之后還是沒(méi)有得到節(jié)點(diǎn)3的回復(fù)。這時(shí)節(jié)點(diǎn)1沒(méi)有收到節(jié)點(diǎn)3的回復(fù)還有可能是他們之間本身連接出了問(wèn)題檩赢,所以節(jié)點(diǎn)1會(huì)首先嘗試與節(jié)點(diǎn)3重新建立連接吕嘀。由于此時(shí)節(jié)點(diǎn)3處于故障狀態(tài),那么節(jié)點(diǎn)1就會(huì)重新建立連接失敗贞瞒。此時(shí)節(jié)點(diǎn)1會(huì)記錄連接建立失敗的時(shí)刻偶房,在實(shí)現(xiàn)中,這個(gè)時(shí)間也是記錄到ping_sent變量中军浆。當(dāng)節(jié)點(diǎn)1發(fā)現(xiàn)與節(jié)點(diǎn)3的斷連時(shí)間超過(guò)了node_timeout之后棕洋,就會(huì)標(biāo)記節(jié)點(diǎn)3為PFAIL,即Possible failure(PFAIL)乒融,可以中文意為主觀下線掰盘。節(jié)點(diǎn)1標(biāo)記節(jié)點(diǎn)3為PFAIL只是說(shuō)明節(jié)點(diǎn)1認(rèn)為節(jié)點(diǎn)3故障了摄悯,但并不代表節(jié)點(diǎn)3真正的故障了,因?yàn)榛蛟S是因?yàn)楣?jié)點(diǎn)1和節(jié)點(diǎn)3之間的網(wǎng)絡(luò)出了問(wèn)題愧捕。當(dāng)節(jié)點(diǎn)1標(biāo)記節(jié)點(diǎn)3為PFAIL后奢驯,節(jié)點(diǎn)1會(huì)通過(guò)Gossip消息把這個(gè)信息發(fā)送給其他節(jié)點(diǎn),接收到信息的節(jié)點(diǎn)會(huì)進(jìn)行節(jié)點(diǎn)3客觀下線狀態(tài)判定晃财。之前我們簡(jiǎn)單介紹過(guò)Redis的Gossip協(xié)議使用叨橱,節(jié)點(diǎn)1每次隨機(jī)向其他幾個(gè)節(jié)點(diǎn)發(fā)送自己視角下的部分節(jié)點(diǎn)狀態(tài)信息。當(dāng)節(jié)點(diǎn)2接收到來(lái)自節(jié)點(diǎn)1關(guān)于節(jié)點(diǎn)3的狀態(tài)判定信息之后断盛,節(jié)點(diǎn)2首先會(huì)把節(jié)點(diǎn)1加入到節(jié)點(diǎn)3的下線報(bào)告列表(Fail Report)中。每個(gè)節(jié)點(diǎn)都會(huì)維護(hù)一個(gè)下線報(bào)告列表愉舔,主要維護(hù)一個(gè)節(jié)點(diǎn)被哪些節(jié)點(diǎn)報(bào)告處于下線狀態(tài)钢猛。比如節(jié)點(diǎn)4在節(jié)點(diǎn)1之前就向節(jié)點(diǎn)2報(bào)告了節(jié)點(diǎn)3的PFAIL信息,當(dāng)節(jié)點(diǎn)2把節(jié)點(diǎn)1加入到節(jié)點(diǎn)3的下線報(bào)告后下線報(bào)告結(jié)果為:Node3:Node4轩缤,Node1命迈,
客觀下線狀態(tài)的判定規(guī)則是: 當(dāng)集群中有超過(guò)1/2數(shù)目的節(jié)點(diǎn)都認(rèn)為節(jié)點(diǎn)3處于PFAIL,那么就判定節(jié)點(diǎn)3為FAIL火的。同時(shí)需要指出的是壶愤,節(jié)點(diǎn)1把Gossip消息發(fā)送給其他節(jié)點(diǎn)后,只有同樣認(rèn)為節(jié)點(diǎn)3處于PFAIL狀態(tài)的節(jié)點(diǎn)才會(huì)去做客觀下線狀態(tài)判定馏鹤。由于節(jié)點(diǎn)2也判定節(jié)點(diǎn)3處于PFAIL征椒,所以節(jié)點(diǎn)2進(jìn)入客觀下線的判定。當(dāng)節(jié)點(diǎn)2發(fā)現(xiàn)有一半以上(包括自己)的主節(jié)點(diǎn)都報(bào)告節(jié)點(diǎn)3處在PFAIL狀態(tài)時(shí)湃累,節(jié)點(diǎn)2標(biāo)記節(jié)點(diǎn)3為FAIL狀態(tài)勃救,并立刻向集群所有節(jié)點(diǎn)廣播這個(gè)信息。
廣播消息
點(diǎn)2判定節(jié)點(diǎn)3為FAIL狀態(tài)后治力,向全集群的節(jié)點(diǎn)廣播Node3的故障消息CLUSTERMSG_TYPE_FAIL蒙秒。當(dāng)集群中的節(jié)點(diǎn)收到此消息時(shí),都會(huì)標(biāo)記節(jié)點(diǎn)3的狀態(tài)為FAIL狀態(tài)宵统,包括節(jié)點(diǎn)3的兩個(gè)子節(jié)點(diǎn)S1晕讲,S2也會(huì)標(biāo)記節(jié)點(diǎn)3為FAIL狀態(tài)。
redis 選主分析
資格檢查:一個(gè)Slave節(jié)點(diǎn)過(guò)長(zhǎng)時(shí)間不與Master節(jié)點(diǎn)通信马澈,那么該節(jié)點(diǎn)就不具備參與競(jìng)選的資格瓢省。
休眠時(shí)間計(jì)算:當(dāng)子節(jié)點(diǎn)都發(fā)現(xiàn)自己具備競(jìng)選資格時(shí),就開(kāi)始參與競(jìng)選箭券,參與選舉的節(jié)點(diǎn)首先隨機(jī)休眠一段時(shí)間净捅,每個(gè)節(jié)點(diǎn)一旦喚醒就立刻向所有的投票節(jié)點(diǎn)發(fā)起拉票請(qǐng)求。對(duì)于投票節(jié)點(diǎn)來(lái)說(shuō)辩块,每一輪選舉中只能投出一票蛔六,投票的規(guī)則就是先到先得荆永。所以一般情況下,都是休眠時(shí)間最短的節(jié)點(diǎn)容易獲得大部分投票国章。
休眠時(shí)間由兩部分組成:
一部分為固定的500ms時(shí)間具钥,這500ms主要是為了等待集群狀態(tài)同步。上面我們講到節(jié)點(diǎn)2會(huì)向集群所有節(jié)點(diǎn)廣播消息液兽,那么這500ms就是等待確保集群的所有節(jié)點(diǎn)都收到了消息并更新了狀態(tài)骂删。
另一部分主要是一個(gè)隨機(jī)的時(shí)間加上由該Slave節(jié)點(diǎn)的排名決定的附加時(shí)間,每個(gè)slave都會(huì)記錄自己從主節(jié)點(diǎn)同步數(shù)據(jù)的復(fù)制偏移量四啰。復(fù)制偏移量越大宁玫,說(shuō)明該節(jié)點(diǎn)與主節(jié)點(diǎn)數(shù)據(jù)保持的越一致,所以我們按照更新?tīng)顟B(tài)的排序來(lái)確定休眠時(shí)間的附加部分柑晒。狀態(tài)更新最近的節(jié)點(diǎn)SLAVE_RANK排名為1欧瘪,那么其休眠的時(shí)間相應(yīng)的也最短,也就意味著該節(jié)點(diǎn)最有可能獲得大部分選票匙赞。DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + SLAVE_RANK * 1000 milliseconds佛掖。
發(fā)起拉票&選舉投票
假設(shè)S1先喚醒,S1喚醒后向所有節(jié)點(diǎn)發(fā)起拉票請(qǐng)求涌庭,即向其他節(jié)點(diǎn)發(fā)送CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST類型的消息芥被。雖然所有的節(jié)點(diǎn)(主節(jié)點(diǎn)+子節(jié)點(diǎn))都會(huì)收到拉票請(qǐng)求,但是只有主節(jié)點(diǎn)才具備投票資格坐榆。當(dāng)其他主節(jié)點(diǎn)接收到拉票請(qǐng)求時(shí)拴魄,如果這一輪投票過(guò)程中該主節(jié)點(diǎn)沒(méi)有投出自己的票,那么就會(huì)把自己的票投給S1猛拴,即向S1回復(fù)FAILOVER_AUTH_ACK消息羹铅。當(dāng)S1接收到來(lái)自其他節(jié)點(diǎn)的ACK消息時(shí)會(huì)統(tǒng)計(jì)自己獲得的票數(shù),當(dāng)S1發(fā)現(xiàn)自己收到集群中一半以上的主節(jié)點(diǎn)的投票時(shí)就會(huì)開(kāi)始執(zhí)行failover愉昆,即替換自己的主節(jié)點(diǎn)過(guò)程职员。
替換節(jié)點(diǎn)(節(jié)點(diǎn)3為舊Master,s1為舊master的slave)
首先標(biāo)記自己為主節(jié)點(diǎn),然后將原來(lái)由節(jié)點(diǎn)3負(fù)責(zé)的slots標(biāo)記為由自己負(fù)責(zé)跛溉,最后向整個(gè)集群廣播現(xiàn)在自己是Master同時(shí)負(fù)責(zé)舊Master所有slots的信息焊切。其他節(jié)點(diǎn)接收到該信息后會(huì)更新自己維護(hù)的S1的狀態(tài)并標(biāo)記S1為主節(jié)點(diǎn),將節(jié)點(diǎn)3負(fù)責(zé)的slots的負(fù)責(zé)節(jié)點(diǎn)設(shè)置為S1節(jié)點(diǎn)芳室。
集群配置更新(節(jié)點(diǎn)3為舊Master专肪,s1、s2為舊master的slave)
當(dāng)S1成為了新的Master之后堪侯,我們是讓S2和節(jié)點(diǎn)3成為新的主節(jié)點(diǎn)S1的Slave節(jié)點(diǎn)嚎尤,去備份S1節(jié)點(diǎn)的數(shù)據(jù)。那這個(gè)過(guò)程是如何進(jìn)行的呢伍宦?這是在各個(gè)節(jié)點(diǎn)信息更新的時(shí)候自動(dòng)實(shí)現(xiàn)的芽死。當(dāng)節(jié)點(diǎn)3故障恢復(fù)重新上線后乏梁,發(fā)現(xiàn)原先本該由自己負(fù)責(zé)的slot被S1負(fù)責(zé)了,那么他就知道自己被替代了关贵,會(huì)自動(dòng)成為S1節(jié)點(diǎn)的子節(jié)點(diǎn)遇骑,當(dāng)S2節(jié)點(diǎn)發(fā)現(xiàn)原先應(yīng)該由其Master節(jié)點(diǎn)3負(fù)責(zé)的slot被S1負(fù)責(zé)了,那么他就知道自己的Master被替代了揖曾,就會(huì)成為S1的Slave節(jié)點(diǎn)落萎。
redis cluster failOver過(guò)程中JedisCluster會(huì)報(bào)錯(cuò),報(bào)錯(cuò)信息如下
hread-1] INFO? c.s.c.r.f.t.RedisClusterReadThread - time=2014-12-17 18:51:49
18:51:49.871 [pool-1-thread-1] ERROR c.s.c.r.f.t.RedisClusterReadThread - Too many Cluster redirections? key=900131
redis.clients.jedis.exceptions.JedisClusterMaxRedirectionsException: Too many Cluster redirections? key=900131
at redis.clients.jedis.JedisClusterCommand.runWithRetries(JedisClusterCommand.java:39) ~[jedis-2.6.2-sohutv-SNAPSHOT.jar:na]
18:51:51.979 [pool-1-thread-1] INFO? c.s.c.r.f.t.RedisClusterReadThread - time=2014-12-17 18:51:51
---------------------------------------------------(重復(fù)的異常太長(zhǎng)了不打印了)----------------------------------------------------
---------------------------------------------------故障恢復(fù)----------------------------------------------------
18:52:02.620 [pool-1-thread-1] INFO? c.s.c.r.f.t.RedisClusterReadThread - success data time=2014-12-17 18:52:02
1):客戶端故障時(shí)間&恢復(fù)時(shí)間:
服務(wù)器停機(jī)實(shí)際時(shí)間: 2014-12-17 18:51:42
客戶端故障時(shí)間: 2014-12-17 18:51:43
恢復(fù)時(shí)間: 2014-12-17 18:52:02, 一共耗時(shí)19秒
手動(dòng)故障轉(zhuǎn)移
向從節(jié)點(diǎn)發(fā)送”CLUSTER? FAILOVER”命令炭剪,使其在主節(jié)點(diǎn)未下線的情況下练链,發(fā)起故障轉(zhuǎn)移流程,升級(jí)為新的主節(jié)點(diǎn)奴拦,而原來(lái)的主節(jié)點(diǎn)降級(jí)為從節(jié)點(diǎn)兑宇;從節(jié)點(diǎn)發(fā)送”CLUSTER? FAILOVER”命令后,流程如下:
? ? ? a:從節(jié)點(diǎn)收到命令后粱坤,向主節(jié)點(diǎn)發(fā)送CLUSTERMSG_TYPE_MFSTART包;
? ? ? b:主節(jié)點(diǎn)收到該包后瓷产,會(huì)將其所有客戶端置于阻塞狀態(tài)站玄,也就是在10s的時(shí)間內(nèi),不再處理客戶端發(fā)來(lái)的命令濒旦;并且在其發(fā)送的心跳包中株旷,會(huì)帶有CLUSTERMSG_FLAG0_PAUSED標(biāo)記;
? ? ? c:從節(jié)點(diǎn)收到主節(jié)點(diǎn)發(fā)來(lái)的尔邓,帶CLUSTERMSG_FLAG0_PAUSED標(biāo)記的心跳包后晾剖,從中獲取主節(jié)點(diǎn)當(dāng)前的復(fù)制偏移量。從節(jié)點(diǎn)等到自己的復(fù)制偏移量達(dá)到該值后梯嗽,才會(huì)開(kāi)始執(zhí)行故障轉(zhuǎn)移流程:發(fā)起選舉齿尽、統(tǒng)計(jì)選票、贏得選舉灯节、升級(jí)為主節(jié)點(diǎn)并更新配置循头;
CLUSTER? FAILOVER 命令支持2個(gè)參數(shù)FORCE和TAKEOVER,
FORCE:從節(jié)點(diǎn)不會(huì)與主節(jié)點(diǎn)進(jìn)行交互炎疆,主節(jié)點(diǎn)也不會(huì)阻塞其客戶端卡骂,而是從節(jié)點(diǎn)立即開(kāi)始故障轉(zhuǎn)移流程:發(fā)起選舉、統(tǒng)計(jì)選票形入、贏得選舉全跨、升級(jí)為主節(jié)點(diǎn)并更新配置。
? ? ? TAKEOVER:從節(jié)點(diǎn)不再發(fā)起選舉亿遂,而是直接將自己升級(jí)為主節(jié)點(diǎn)浓若,接手原主節(jié)點(diǎn)的槽位渺杉,增加自己的configEpoch后更新配置。
? ? ? FORCE和TAKEOVER參數(shù)七嫌,主節(jié)點(diǎn)可以已經(jīng)下線少办;而不使用任何選項(xiàng),只發(fā)送”CLUSTER? FAILOVER”命令的話诵原,主節(jié)點(diǎn)必須在線英妓。
configEpoch沖突問(wèn)題
如果一個(gè)主節(jié)點(diǎn)檢測(cè)到另外一個(gè)主節(jié)點(diǎn)宣稱自己具有相同的configEpoch
且如果節(jié)點(diǎn)的ID比另外一個(gè)宣稱相同configEpoch的節(jié)點(diǎn)在邏輯上更小.
那么它會(huì)對(duì)自己的currentEpoch+1,然后用其結(jié)果作為新的configEpoch.
無(wú)論發(fā)生什么,只要有一個(gè)具有相同configEpoch的節(jié)點(diǎn)集合,除了具有最大節(jié)點(diǎn)ID的節(jié)點(diǎn)外,所有節(jié)點(diǎn)都會(huì)對(duì)自己的currentEpoch+1直到最終每個(gè)節(jié)點(diǎn)都去的了唯一的configEpoch.
這個(gè)機(jī)制同時(shí)確保了在新集群創(chuàng)建后,所有的節(jié)點(diǎn)起初都是設(shè)置了不同的configEpoch(即使實(shí)際上沒(méi)有用)因?yàn)閞edis-trib工具會(huì)在啟動(dòng)時(shí)確保使用CONFIG SET-CONFIG-EPOCH.然而如果處于某種原因,節(jié)點(diǎn)沒(méi)有被設(shè)置,它也會(huì)自動(dòng)使用一個(gè)不同的配置epoch更新自己的配置.
數(shù)據(jù)遷移
數(shù)據(jù)遷移狀態(tài):MIGRATING狀態(tài)、IMPORTING狀態(tài)绍赛,當(dāng)槽x從Node A向Node B遷移時(shí)蔓纠,Node A和Node B都會(huì)有這個(gè)槽x,Node A上槽x的狀態(tài)設(shè)置為MIGRATING吗蚌,Node B上槽x的狀態(tài)被設(shè)置為IMPORTING腿倚。
MIGRATING狀態(tài)
1)如果key存在則成功處理
2)如果key不存在,則返回客戶端ASK蚯妇,客戶端根據(jù)ASK首先發(fā)送ASKING命令到目標(biāo)節(jié)點(diǎn)敷燎,然后發(fā)送請(qǐng)求的命令到目標(biāo)節(jié)點(diǎn)
3)當(dāng)key包含多個(gè)命令,
? ? a)如果都存在則成功處理
? ? b)如果都不存在箩言,則返回客戶端ASK
? ? c)如果一部分存在硬贯,則返回客戶端TRYAGAIN,通知客戶端稍后重試陨收,這樣當(dāng)所有的? ? ? ? key都遷移完畢的時(shí)候客戶端重試請(qǐng)求的時(shí)候回得到ASK饭豹,然后經(jīng)過(guò)一次重定向就? ? ? ? ? 可以獲取這批鍵
4)此時(shí)不刷新客戶端中node的映射關(guān)系
IMPORTING狀態(tài)
1)如果key不在該節(jié)點(diǎn)上,會(huì)被MOVED重定向务漩,刷新客戶端中node的映射關(guān)系
2)如果是ASKING命令則命令會(huì)被執(zhí)行拄衰,key不在遷移的節(jié)點(diǎn)已經(jīng)被遷移到目標(biāo)的節(jié)點(diǎn)
3)Key不存在則新建
請(qǐng)求重定向
a)MOVED錯(cuò)誤
1.請(qǐng)求的key對(duì)應(yīng)的槽不在該節(jié)點(diǎn)上,節(jié)點(diǎn)將查看自身內(nèi)部所保存的哈希槽到節(jié)點(diǎn)ID的映射記錄饵骨,節(jié)點(diǎn)回復(fù)一個(gè)MOVED錯(cuò)誤翘悉。
2.需要客戶端進(jìn)行再次重試。
b)ASK錯(cuò)誤
1.請(qǐng)求的key對(duì)應(yīng)的槽目前的狀態(tài)屬于MIGRATING狀態(tài)宏悦,并且當(dāng)前節(jié)點(diǎn)找不到這個(gè)key了镐确,節(jié)點(diǎn)回復(fù)ASK錯(cuò)誤。ASK會(huì)把對(duì)應(yīng)槽的IMPORTING節(jié)點(diǎn)返回給你饼煞,告訴你去IMPORTING的節(jié)點(diǎn)嘗試找找源葫。
2.客戶端進(jìn)行重試首先發(fā)送ASKING命令,節(jié)點(diǎn)將為客戶端設(shè)置一個(gè)一次性的標(biāo)志(flag)砖瞧,使得客戶端可以執(zhí)行一次針對(duì)IMPORTING狀態(tài)的槽的命令請(qǐng)求息堂,然后再發(fā)送真正的命令請(qǐng)求。
3.不必更新客戶端所記錄的槽至節(jié)點(diǎn)的映射。