Redis中主從復(fù)制、Sentinel、集群有什么用

這篇文章主要為大家展示了“redis中主從復(fù)制、Sentinel、集群有什么用”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Redis中主從復(fù)制、Sentinel、集群有什么用”這篇文章吧。

天水網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)從2013年創(chuàng)立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專(zhuān)注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)。

一、主從復(fù)制

1、簡(jiǎn)介

主從復(fù)制是Redis分布式的基石,也是Redis高可用的保障。在Redis中,被復(fù)制的服務(wù)器稱(chēng)為主服務(wù)器(Master),對(duì)主服務(wù)器進(jìn)行復(fù)制的服務(wù)器稱(chēng)為從服務(wù)器(Slave)。

Redis中主從復(fù)制、Sentinel、集群有什么用

主從復(fù)制的配置非常簡(jiǎn)單,有三種方式(其中IP-主服務(wù)器IP地址/PORT-主服務(wù)器Redis服務(wù)端口):

  • 配置文件——redis.conf文件中,配置slaveof ip port

  • 命令——進(jìn)入Redis客戶(hù)端執(zhí)行slaveof ip port

  • 啟動(dòng)參數(shù)—— ./redis-server --slaveof ip port

2、主從復(fù)制的演進(jìn)

Redis的主從復(fù)制機(jī)制,并不是一開(kāi)始就像6.x版本一樣完善,而是一個(gè)版本一個(gè)版本迭代而來(lái)的。它大體上經(jīng)過(guò)三個(gè)版本的迭代:

  • 2.8以前

  • 2.8~4.0

  • 4.0以后

隨著版本的增長(zhǎng),Redis主從復(fù)制機(jī)制逐漸完善;但是他們的本質(zhì)都是圍繞同步(sync)和命令傳播(command propagate)兩個(gè)操作展開(kāi):

  • 同步(sync):指的是將從服務(wù)器的數(shù)據(jù)狀態(tài)更新至主服務(wù)器當(dāng)前的數(shù)據(jù)狀態(tài),主要發(fā)生在初始化或后續(xù)的全量同步。

  • 命令傳播(command propagate):當(dāng)主服務(wù)器的數(shù)據(jù)狀態(tài)被修改(寫(xiě)/刪除等),主從之間的數(shù)據(jù)狀態(tài)不一致時(shí),主服務(wù)將發(fā)生數(shù)據(jù)改變的命令傳播給從服務(wù)器,讓主從服務(wù)器之間的狀態(tài)重回一致。

2.1 版本2.8以前

2.1.1 同步

2.8以前的版本,從服務(wù)器對(duì)主服務(wù)器的同步需要從服務(wù)器向主服務(wù)器發(fā)生sync命令來(lái)完成:

Redis中主從復(fù)制、Sentinel、集群有什么用

  • 從服務(wù)器接收到客戶(hù)端發(fā)送的slaveof ip prot命令,從服務(wù)器根據(jù)ip:port向主服務(wù)器創(chuàng)建套接字連接

  • 套接字成功連接到主服務(wù)器后,從服務(wù)器會(huì)為這個(gè)套接字連接關(guān)聯(lián)一個(gè)專(zhuān)門(mén)用于處理復(fù)制工作的文件事件處理器,處理后續(xù)的主服務(wù)器發(fā)送的RDB文件和傳播的命令

  • 開(kāi)始進(jìn)行復(fù)制,從服務(wù)器向主服務(wù)器發(fā)送sync命令

  • 主服務(wù)器接收到sync命令后,執(zhí)行bgsave命令,主服務(wù)器主進(jìn)程fork的子進(jìn)程會(huì)生成一個(gè)RDB文件,同時(shí)將RDB快照產(chǎn)生后的所有寫(xiě)操作記錄在緩沖區(qū)中

  • bgsave命令執(zhí)行完成后,主服務(wù)器將生成的RDB文件發(fā)送給從服務(wù)器,從服務(wù)器接收到RDB文件后,首先會(huì)清除本身的全部數(shù)據(jù),然后載入RDB文件,將自己的數(shù)據(jù)狀態(tài)更新成主服務(wù)器的RDB文件的數(shù)據(jù)狀態(tài)

  • 主服務(wù)器將緩沖區(qū)的寫(xiě)命令發(fā)送給從服務(wù)器,從服務(wù)器接收命令,并執(zhí)行。

  • 主從復(fù)制同步步驟完成

2.1.2 命令傳播

當(dāng)同步工作完成之后,主從之間需要通過(guò)命令傳播來(lái)維持?jǐn)?shù)據(jù)狀態(tài)的一致性。 如下圖,當(dāng)前主從服務(wù)器之間完成同步工作之后,主服務(wù)接收客戶(hù)端的DEL K6指令后刪除了K6,此時(shí)從服務(wù)器仍然存在K6,主從數(shù)據(jù)狀態(tài)并不一致。為了維持主從服務(wù)器狀態(tài)一致,主服務(wù)器會(huì)將導(dǎo)致自己數(shù)據(jù)狀態(tài)發(fā)生改變的命令傳播到從服務(wù)器執(zhí)行,當(dāng)從服務(wù)器也執(zhí)行了相同的命令之后,主從服務(wù)器之間的數(shù)據(jù)狀態(tài)將會(huì)保持一致。

Redis中主從復(fù)制、Sentinel、集群有什么用

2.1.3 缺陷

從上面看不出2.8以前版本的主從復(fù)制有什么缺陷,這是因?yàn)槲覀冞€沒(méi)有考慮網(wǎng)絡(luò)波動(dòng)的情況。了解分布式的兄弟們肯定聽(tīng)說(shuō)過(guò)CAP理論,CAP理論是分布式存儲(chǔ)系統(tǒng)的基石,在CAP理論中P(partition網(wǎng)絡(luò)分區(qū))必然存在,Redis主從復(fù)制也不例外。當(dāng)主從服務(wù)器之間出現(xiàn)網(wǎng)絡(luò)故障,導(dǎo)致一段時(shí)間內(nèi)從服務(wù)器與主服務(wù)器之間無(wú)法通信,當(dāng)從服務(wù)器重新連接上主服務(wù)器時(shí),如果主服務(wù)器在這段時(shí)間內(nèi)數(shù)據(jù)狀態(tài)發(fā)生了改變,那么主從服務(wù)器之間將出現(xiàn)數(shù)據(jù)狀態(tài)不一致。 在Redis 2.8以前的主從復(fù)制版本中,解決這種數(shù)據(jù)狀態(tài)不一致的方式是通過(guò)重新發(fā)送sync命令來(lái)實(shí)現(xiàn)。雖然sync能保證主從服務(wù)器數(shù)據(jù)狀態(tài)一致,但是很明顯sync是一個(gè)非常消耗資源的操作。

sync命令執(zhí)行,主從服務(wù)器需要占用的資源:

  • 主服務(wù)器執(zhí)行BGSAVE生成RDB文件,會(huì)占用大量CPU、磁盤(pán)I/O和內(nèi)存資源

  • 主服務(wù)器將生成的RDB文件發(fā)送給從服務(wù)器,會(huì)占用大量網(wǎng)絡(luò)帶寬,

  • 從服務(wù)器接收RDB文件并載入,會(huì)導(dǎo)致從服務(wù)器阻塞,無(wú)法提供服務(wù)

從上面三點(diǎn)可以看出,sync命令不僅會(huì)導(dǎo)致主服務(wù)器的響應(yīng)能力下降,也會(huì)導(dǎo)致從服務(wù)器在此期間拒絕對(duì)外提供服務(wù)。

2.2 版本2.8-4.0

2.2.1 改進(jìn)點(diǎn)

針對(duì)2.8以前的版本,Redis在2.8之后對(duì)從服務(wù)器重連后的數(shù)據(jù)狀態(tài)同步進(jìn)行了改進(jìn)。改進(jìn)的方向是減少全量同步(full resynchronizaztion)的發(fā)生,盡可能使用增量同步(partial resynchronization)。在2.8版本之后使用psync命令代替了sync命令來(lái)執(zhí)行同步操作,psync命令同時(shí)具備全量同步和增量同步的功能:

  • 全量同步與上一版本(sync)一致

  • 增量同步中對(duì)于斷線重連后的復(fù)制,會(huì)根據(jù)情況采取不同措施;如果條件允許,仍然只發(fā)送從服務(wù)缺失的部分?jǐn)?shù)據(jù)。

2.2.2 psync如何實(shí)現(xiàn)

Redis為了實(shí)現(xiàn)從服務(wù)器斷線重連后的增量同步,增加了三個(gè)輔助參數(shù):

  • 復(fù)制偏移量(replication offset)

  • 積壓緩沖區(qū)(replication backlog)

  • 服務(wù)器運(yùn)行id(run id)

2.2.2.1 復(fù)制偏移量

在主服務(wù)器和從服務(wù)器內(nèi)都會(huì)維護(hù)一個(gè)復(fù)制偏移量

  • 主服務(wù)器向從服務(wù)發(fā)送數(shù)據(jù),傳播N個(gè)字節(jié)的數(shù)據(jù),主服務(wù)的復(fù)制偏移量增加N

  • 從服務(wù)器接收主服務(wù)器發(fā)送的數(shù)據(jù),接收N個(gè)字節(jié)的數(shù)據(jù),從服務(wù)器的復(fù)制偏移量增加N

正常同步的情況如下:

Redis中主從復(fù)制、Sentinel、集群有什么用

通過(guò)對(duì)比主從服務(wù)器之間的復(fù)制偏移量是否相等,能夠得知主從服務(wù)器之間的數(shù)據(jù)狀態(tài)是否保持一致。 假設(shè)此時(shí)A/B正常傳播,C從服務(wù)器斷線,那么將出現(xiàn)如下情況:

Redis中主從復(fù)制、Sentinel、集群有什么用

很明顯有了復(fù)制偏移量之后,從服務(wù)器C斷線重連后,主服務(wù)器只需要發(fā)送從服務(wù)器缺少的100字節(jié)數(shù)據(jù)即可。但是主服務(wù)器又是如何知道從服務(wù)器缺少的是那些數(shù)據(jù)呢?

2.2.2.2 復(fù)制積壓緩沖區(qū)

復(fù)制積壓緩沖區(qū)是一個(gè)固定長(zhǎng)度的隊(duì)列,默認(rèn)為1MB大小。當(dāng)主服務(wù)器數(shù)據(jù)狀態(tài)發(fā)生改變,主服務(wù)器將數(shù)據(jù)同步給從服務(wù)器的同時(shí)會(huì)另存一份到復(fù)制積壓緩沖區(qū)中。

Redis中主從復(fù)制、Sentinel、集群有什么用

復(fù)制積壓緩沖區(qū)為了能和偏移量進(jìn)行匹配,它不僅存儲(chǔ)了數(shù)據(jù)內(nèi)容,還記錄了每個(gè)字節(jié)對(duì)應(yīng)的偏移量:

Redis中主從復(fù)制、Sentinel、集群有什么用

當(dāng)從服務(wù)器斷線重連后,從服務(wù)器通過(guò)psync命令將自己的復(fù)制偏移量(offset)發(fā)送給主服務(wù)器,主服務(wù)器便可通過(guò)這個(gè)偏移量來(lái)判斷進(jìn)行增量傳播還是全量同步。

  • 如果偏移量offset+1的數(shù)據(jù)仍然在復(fù)制積壓緩沖區(qū)中,那么進(jìn)行增量同步操作

  • 反之進(jìn)行全量同步操作,與sync一致

Redis的復(fù)制積壓緩沖區(qū)的大小默認(rèn)為1MB,如果需要自定義應(yīng)該如何設(shè)置呢?很明顯,我們希望能盡可能的使用增量同步,但是又不希望緩沖區(qū)占用過(guò)多的內(nèi)存空間。那么我們可以通過(guò)預(yù)估Redis從服務(wù)斷線后重連的時(shí)間T,Redis主服務(wù)器每秒接收的寫(xiě)命令的內(nèi)存大小M,來(lái)設(shè)置復(fù)制積壓緩沖區(qū)的大小S。

S = 2 * M * T

注意這里擴(kuò)大2倍是為了留有一定的余地,保證絕大部分的斷線重連都能采用增量同步。

2.2.2.3 服務(wù)器運(yùn)行 ID

看到這里是不是再想上面已經(jīng)可以實(shí)現(xiàn)斷線重連的增量同步了,還要運(yùn)行ID干嘛?其實(shí)還有一種情況沒(méi)考慮,就是當(dāng)主服務(wù)器宕機(jī)后,某臺(tái)從服務(wù)器被選舉成為新的主服務(wù)器,這種情況我們就通過(guò)比較運(yùn)行ID來(lái)區(qū)分。

  • 運(yùn)行ID(run id)是服務(wù)器啟動(dòng)時(shí)自動(dòng)生成的40個(gè)隨機(jī)的十六進(jìn)制字符串,主服務(wù)和從服務(wù)器均會(huì)生成運(yùn)行ID

  • 當(dāng)從服務(wù)器首次同步主服務(wù)器的數(shù)據(jù)時(shí),主服務(wù)器會(huì)發(fā)送自己的運(yùn)行ID給從服務(wù)器,從服務(wù)器會(huì)保存在RDB文件中

  • 當(dāng)從服務(wù)器斷線重連后,從服務(wù)器會(huì)向主服務(wù)器發(fā)送之前保存的主服務(wù)器運(yùn)行ID,如果服務(wù)器運(yùn)行ID匹配,則證明主服務(wù)器未發(fā)生更改,可以嘗試進(jìn)行增量同步

  • 如果服務(wù)器運(yùn)行ID不匹配,則進(jìn)行全量同步

2.2.3 完整的psync

完整的psync過(guò)程非常的復(fù)雜,在2.8-4.0的主從復(fù)制版本中已經(jīng)做到了非常完善。psync命令發(fā)送的參數(shù)如下:

psync

當(dāng)從服務(wù)器沒(méi)有復(fù)制過(guò)任何主服務(wù)器(并不是主從第一次復(fù)制,因?yàn)橹鞣?wù)器可能會(huì)變化,而是從服務(wù)器第一次全量同步),從服務(wù)器將會(huì)發(fā)送:

psync ? -1

Redis中主從復(fù)制、Sentinel、集群有什么用

一起完整的psync流程如下圖:

Redis中主從復(fù)制、Sentinel、集群有什么用

  • 從服務(wù)器接收到SLAVEOF 127.0.0.1 6379命令

  • 從服務(wù)器返回OK給命令發(fā)起方(這里是異步操作,先返回OK,再保存地址和端口信息)

  • 從服務(wù)器將IP地址和端口信息保存到Master Host和Master Port中

  • 從服務(wù)器根據(jù)Master Host和Master Port主動(dòng)向主服務(wù)器發(fā)起套接字連接,同時(shí)從服務(wù)將會(huì)未這個(gè)套接字連接關(guān)聯(lián)一個(gè)專(zhuān)門(mén)用于文件復(fù)制工作的文件事件處理器,用于后續(xù)的RDB文件復(fù)制等工作

  • 主服務(wù)器接收到從服務(wù)器的套接字連接請(qǐng)求,為該請(qǐng)求創(chuàng)建對(duì)應(yīng)的套接字連接之后,并將從服務(wù)器看著一個(gè)客戶(hù)端(在主從復(fù)制中,主服務(wù)器和從服務(wù)器之間其實(shí)互為客戶(hù)端和服務(wù)端)

  • 套接字連接建立完成,從服務(wù)器主動(dòng)向主服務(wù)發(fā)送PING命令,如果在指定的超時(shí)時(shí)間內(nèi)主服務(wù)器返回PONG,則證明套接字連接可用,否則斷開(kāi)重連

  • 如果主服務(wù)器設(shè)置了密碼(masterauth),那么從服務(wù)器向主服務(wù)器發(fā)送AUTH masterauth命令,進(jìn)行身份驗(yàn)證。注意,如果從服務(wù)器發(fā)送了密碼,主服務(wù)并未設(shè)置密碼,此時(shí)主服務(wù)會(huì)發(fā)送no password is set錯(cuò)誤;如果主服務(wù)器需要密碼,而從服務(wù)器未發(fā)送密碼,此時(shí)主服務(wù)器會(huì)發(fā)送NOAUTH錯(cuò)誤;如果密碼不匹配,主服務(wù)器會(huì)發(fā)送invalid password錯(cuò)誤。

  • 從服務(wù)器向主服務(wù)器發(fā)送REPLCONF listening-port xxxx(xxxx表示從服務(wù)器的端口)。主服務(wù)器接收到該命令后會(huì)將數(shù)據(jù)保存起來(lái),當(dāng)客戶(hù)端使用INFO replication查詢(xún)主從信息時(shí)能夠返回?cái)?shù)據(jù)

  • 從服務(wù)器發(fā)送psync命令,此步驟請(qǐng)查看上圖psync的兩種情況

  • 主服務(wù)器與從服務(wù)器之間互為客戶(hù)端,進(jìn)行數(shù)據(jù)的請(qǐng)求/響應(yīng)

  • 主服務(wù)器與從服務(wù)器之間通過(guò)心跳包機(jī)制,判斷連接是否斷開(kāi)。從服務(wù)器每個(gè)1秒向主服務(wù)器發(fā)送命令,REPLCONF ACL offset(從服務(wù)器的復(fù)制偏移量),該機(jī)制可以保證主從之間數(shù)據(jù)的正確同步,如果偏移量不相等,主服務(wù)器將會(huì)采取增量/全量同步措施來(lái)保證主從之間數(shù)據(jù)狀態(tài)一致(增量/全量的選擇取決于,offset+1的數(shù)據(jù)是否仍在復(fù)制積壓緩沖區(qū)中)

2.3 版本4.0

Redis 2.8-4.0版本仍然有一些改進(jìn)的空間,當(dāng)主服務(wù)器切換時(shí),是否也能進(jìn)行增量同步呢?因此Redis 4.0版本針對(duì)這個(gè)問(wèn)題做了優(yōu)化處理,psync升級(jí)為psync2.0。 psync2.0 拋棄了服務(wù)器運(yùn)行ID,采用了replid和replid2來(lái)代替,其中replid存儲(chǔ)的是當(dāng)前主服務(wù)器的運(yùn)行ID,replid2保存的是上一個(gè)主服務(wù)器運(yùn)行ID。

  • 復(fù)制偏移量(replication offset)

  • 積壓緩沖區(qū)(replication backlog)

  • 主服務(wù)器運(yùn)行id(replid)

  • 上個(gè)主服務(wù)器運(yùn)行id(replid2)

通過(guò)replid和replid2我們可以解決主服務(wù)器切換時(shí),增量同步的問(wèn)題:

  • 如果replid等于當(dāng)前主服務(wù)器的運(yùn)行id,那么判斷同步方式增量/全量同步

  • 如果replid不相等,則判斷replid2是否相等(是否同屬于上一個(gè)主服務(wù)器的從服務(wù)器),如果相等,仍然可以選擇增量/全量同步,如果不相等則只能進(jìn)行全量同步。

二、Sentinel

1、簡(jiǎn)介

主從復(fù)制奠定了Redis分布式的基礎(chǔ),但是普通的主從復(fù)制并不能達(dá)到高可用的狀態(tài)。在普通的主從復(fù)制模式下,如果主服務(wù)器宕機(jī),就只能通過(guò)運(yùn)維人員手動(dòng)切換主服務(wù)器,很顯然這種方案并不可取。 針對(duì)上述情況,Redis官方推出了可抵抗節(jié)點(diǎn)故障的高可用方案——Redis Sentinel(哨兵)。Redis Sentinel(哨兵):由一個(gè)或多個(gè)Sentinel實(shí)例組成的Sentinel系統(tǒng),它可以監(jiān)視任意多個(gè)主從服務(wù)器,當(dāng)監(jiān)視的主服務(wù)器宕機(jī)時(shí),自動(dòng)下線主服務(wù)器,并且擇優(yōu)選取從服務(wù)器升級(jí)為新的主服務(wù)器。

如下示例:當(dāng)舊Master下線時(shí)長(zhǎng)超過(guò)用戶(hù)設(shè)定的下線時(shí)長(zhǎng)上限,Sentinel系統(tǒng)就會(huì)對(duì)舊Master執(zhí)行故障轉(zhuǎn)移操作,故障轉(zhuǎn)移操作包含三個(gè)步驟:

  • 在Slave中選擇數(shù)據(jù)最新的作為新的Master

  • 向其他Slave發(fā)送新的復(fù)制指令,讓其他從服務(wù)器成為新的Master的Slave

  • 繼續(xù)監(jiān)視舊Master,如果其上線則將舊Master設(shè)置為新Master的Slave

Redis中主從復(fù)制、Sentinel、集群有什么用

本文基于如下資源清單進(jìn)行開(kāi)展:

IP地址節(jié)點(diǎn)角色端口
192.168.211.104Redis Master/ Sentinel6379/26379
192.168.211.105Redis Slave/ Sentinel6379/26379
192.168.211.106Redis Slave/ Sentinel6379/26379

2、Sentinel初始化與網(wǎng)絡(luò)連接

Sentinel并沒(méi)有什么特別神奇的地方,它就是一個(gè)更加簡(jiǎn)單的Redis服務(wù)器,在Sentinel啟動(dòng)的時(shí)候它會(huì)加載不同的命令表和配置文件,因此從本質(zhì)上來(lái)講Sentinel就是一個(gè)擁有較少命令和部分特殊功能的Redis服務(wù)。當(dāng)一個(gè)Sentinel啟動(dòng)時(shí)它需要經(jīng)歷如下步驟:

  • 初始化Sentinel服務(wù)器

  • 替換普通Redis代碼為Sentinel的專(zhuān)用代碼

  • 初始化Sentinel狀態(tài)

  • 根據(jù)用戶(hù)給定的Sentinel配置文件,初始化Sentinel監(jiān)視的主服務(wù)器列表

  • 創(chuàng)建連接主服務(wù)器的網(wǎng)絡(luò)連接

  • 根據(jù)主服務(wù)獲取從服務(wù)器信息,創(chuàng)建連接從服務(wù)器的網(wǎng)絡(luò)連接

  • 根據(jù)發(fā)布/訂閱獲取Sentinel信息,創(chuàng)建Sentinel之間的網(wǎng)絡(luò)連接

2.1 初始化Sentinel服務(wù)器

Sentinel本質(zhì)上就是一個(gè)Redis服務(wù)器,因此啟動(dòng)Sentinel需要啟動(dòng)一個(gè)Redis服務(wù)器,但是Sentinel并不需要讀取RDB/AOF文件來(lái)還原數(shù)據(jù)狀態(tài)。

2.2 替換普通Redis代碼為Sentinel的專(zhuān)用代碼

Sentinel用于較少的Redis命令,大部分命令在Sentinel客戶(hù)端都不支持,并且Sentinel擁有一些特殊的功能,這些需要Sentinel在啟動(dòng)時(shí)將Redis服務(wù)器使用的代碼替換為Sentinel的專(zhuān)用代碼。在此期間Sentinel會(huì)載入與普通Redis服務(wù)器不同的命令表。 Sentinel不支持SET、DBSIZE等命令;保留支持PING、PSUBSCRIBE、SUBSCRIBE、UNSUBSCRIBE、INFO等指令;這些指令在Sentinel工作中提供了保障。

2.3 初始化Sentinel狀態(tài)

裝載Sentinel的特有代碼之后,Sentinel會(huì)初始化sentinelState結(jié)構(gòu),該結(jié)構(gòu)用于存儲(chǔ)Sentinel相關(guān)的狀態(tài)信息,其中最重要的就是masters字典。

struct sentinelState {
   
    //當(dāng)前紀(jì)元,故障轉(zhuǎn)移使用
 uint64_t current_epoch; 
  
    // Sentinel監(jiān)視的主服務(wù)器信息 
    // key -> 主服務(wù)器名稱(chēng) 
    // value -> 指向sentinelRedisInstance指針
    dict *masters; 
    // ...
} sentinel;

2.4 初始化Sentinel監(jiān)視的主服務(wù)器列表

Sentinel監(jiān)視的主服務(wù)器列表保存在sentinelState的masters字典中,當(dāng)sentinelState創(chuàng)建之后,開(kāi)始對(duì)Sentinel監(jiān)視的主服務(wù)器列表進(jìn)行初始化。

  • masters的key是主服務(wù)的名字

  • masters的value是一個(gè)指向sentinelRedisInstance指針

主服務(wù)器的名字由我們sentinel.conf配置文件指定,如下主服務(wù)器名字為redis-master(我這里是一主二從的配置):

daemonize yes
port 26379
protected-mode no
dir "/usr/local/soft/redis-6.2.4/sentinel-tmp"
sentinel monitor redis-master 192.168.211.104 6379 2
sentinel down-after-milliseconds redis-master 30000
sentinel failover-timeout redis-master 180000
sentinel parallel-syncs redis-master 1

sentinelRedisInstance實(shí)例保存了Redis服務(wù)器的信息(主服務(wù)器、從服務(wù)器、Sentinel信息都保存在這個(gè)實(shí)例中)。

typedef struct sentinelRedisInstance {
 
    // 標(biāo)識(shí)值,標(biāo)識(shí)當(dāng)前實(shí)例的類(lèi)型和狀態(tài)。如SRI_MASTER、SRI_SLVAE、SRI_SENTINEL
    int flags;
    
    // 實(shí)例名稱(chēng) 主服務(wù)器為用戶(hù)配置實(shí)例名稱(chēng)、從服務(wù)器和Sentinel為ip:port
    char *name;
    
    // 服務(wù)器運(yùn)行ID
    char *runid;
    
    //配置紀(jì)元,故障轉(zhuǎn)移使用
 uint64_t config_epoch; 
    
    // 實(shí)例地址
    sentinelAddr *addr;
    
    // 實(shí)例判斷為主觀下線的時(shí)長(zhǎng) sentinel down-after-milliseconds redis-master 30000
    mstime_t down_after_period; 
    
    // 實(shí)例判斷為客觀下線所需支持的投票數(shù) sentinel monitor redis-master 192.168.211.104 6379 2
    int quorum;
    
    // 執(zhí)行故障轉(zhuǎn)移操作時(shí),可以同時(shí)對(duì)新的主服務(wù)器進(jìn)行同步的從服務(wù)器數(shù)量 sentinel parallel-syncs redis-master 1
    int parallel-syncs;
    
    // 刷新故障遷移狀態(tài)的最大時(shí)限 sentinel failover-timeout redis-master 180000
 mstime_t failover_timeout;
    
    // ...
} sentinelRedisInstance;

根據(jù)上面的一主二從配置將會(huì)得到如下實(shí)例結(jié)構(gòu):

Redis中主從復(fù)制、Sentinel、集群有什么用

2.5 創(chuàng)建連接主服務(wù)器的網(wǎng)絡(luò)連接

當(dāng)實(shí)例結(jié)構(gòu)初始化完成之后,Sentinel將會(huì)開(kāi)始創(chuàng)建連接Master的網(wǎng)絡(luò)連接,這一步Sentinel將成為Master的客戶(hù)端。 Sentinel和Master之間會(huì)創(chuàng)建一個(gè)命令連接和一個(gè)訂閱連接:

  • 命令連接用于獲取主從信息

  • 訂閱連接用于Sentinel之間進(jìn)行信息廣播,每個(gè)Sentinel和自己監(jiān)視的主從服務(wù)器之間會(huì)訂閱_sentinel_:hello頻道(注意Sentinel之間不會(huì)創(chuàng)建訂閱連接,它們通過(guò)訂閱_sentinel_:hello頻道來(lái)獲取其他Sentinel的初始信息)

Redis中主從復(fù)制、Sentinel、集群有什么用

Sentinel在創(chuàng)建命令連接完成之后,每隔10秒鐘向Master發(fā)送一次INFO指令,通過(guò)Master的回復(fù)信息可以獲得兩方面的知識(shí):

  • Master本身的信息

  • Master下的Slave信息

Redis中主從復(fù)制、Sentinel、集群有什么用

2.6 創(chuàng)建連接從服務(wù)器的網(wǎng)絡(luò)連接

根據(jù)主服務(wù)獲取從服務(wù)器信息,Sentinel可以創(chuàng)建到Slave的網(wǎng)絡(luò)連接,Sentinel和Slave之間也會(huì)創(chuàng)建命令連接和訂閱連接。

Redis中主從復(fù)制、Sentinel、集群有什么用

當(dāng)Sentinel和Slave之間創(chuàng)建網(wǎng)絡(luò)連接之后,Sentinel成為了Slave的客戶(hù)端,Sentinel也會(huì)每隔10秒鐘通過(guò)INFO指令請(qǐng)求Slave獲取服務(wù)器信息。 到這一步Sentinel獲取到了Master和Slave的相關(guān)服務(wù)器數(shù)據(jù)。這其中比較重要的信息如下:

  • 服務(wù)器ip和port

  • 服務(wù)器運(yùn)行id run id

  • 服務(wù)器角色role

  • 服務(wù)器連接狀態(tài)mater_link_status

  • Slave復(fù)制偏移量slave_repl_offset(故障轉(zhuǎn)移中選舉新的Master需要使用)

  • Slave優(yōu)先級(jí)slave_priority

此時(shí)實(shí)例結(jié)構(gòu)信息如下所示:

Redis中主從復(fù)制、Sentinel、集群有什么用

2.7 創(chuàng)建Sentinel之間的網(wǎng)絡(luò)連接

此時(shí)是不是還有疑問(wèn),Sentinel之間是怎么互相發(fā)現(xiàn)對(duì)方并且相互通信的,這個(gè)就和上面Sentinel與自己監(jiān)視的主從之間訂閱_sentinel_:hello頻道有關(guān)了。 Sentinel會(huì)與自己監(jiān)視的所有Master和Slave之間訂閱_sentinel_:hello頻道,并且Sentinel每隔2秒鐘向_sentinel_:hello頻道發(fā)送一條消息,消息內(nèi)容如下:

PUBLISH sentinel:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_ip>,<m_port>,<m_runid>,<m_epoch>"

其中s代碼Sentinel,m代表Master;ip表示IP地址,port表示端口、runid表示運(yùn)行id、epoch表示配置紀(jì)元。

多個(gè)Sentinel在配置文件中會(huì)配置相同的主服務(wù)器ip和端口信息,因此多個(gè)Sentinel均會(huì)訂閱_sentinel_:hello頻道,通過(guò)頻道接收到的信息就可獲取到其他Sentinel的ip和port,其中有如下兩點(diǎn)需要注意:

  • 如果獲取到的runid與Sentinel自己的runid相同,說(shuō)明消息是自己發(fā)布的,直接丟棄

  • 如果不相同,則說(shuō)明接收到的消息是其他Sentinel發(fā)布的,此時(shí)需要根據(jù)ip和port去更新或新增Sentinel實(shí)例數(shù)據(jù)

Sentinel之間不會(huì)創(chuàng)建訂閱連接,它們只會(huì)創(chuàng)建命令連接:

Redis中主從復(fù)制、Sentinel、集群有什么用

此時(shí)實(shí)例結(jié)構(gòu)信息如下所示:

Redis中主從復(fù)制、Sentinel、集群有什么用

3、Sentinel工作

Sentinel最主要的工作就是監(jiān)視Redis服務(wù)器,當(dāng)Master實(shí)例超出預(yù)設(shè)的時(shí)限后切換新的Master實(shí)例。這其中有很多細(xì)節(jié)工作,大致分為檢測(cè)Master是否主觀下線、檢測(cè)Master是否客觀下線、選舉領(lǐng)頭Sentinel、故障轉(zhuǎn)移四個(gè)步驟。

3.1 檢測(cè)Master是否主觀下線

Sentinel每隔1秒鐘,向sentinelRedisInstance實(shí)例中的所有Master、Slave、Sentinel發(fā)送PING命令,通過(guò)其他服務(wù)器的回復(fù)來(lái)判斷其是否仍然在線。

sentinel down-after-milliseconds redis-master 30000

在Sentinel的配置文件中,當(dāng)Sentinel PING的實(shí)例在連續(xù)down-after-milliseconds配置的時(shí)間內(nèi)返回?zé)o效命令,則當(dāng)前Sentinel認(rèn)為其主觀下線。Sentinel的配置文件中配置的down-after-milliseconds將會(huì)對(duì)其sentinelRedisInstance實(shí)例中的所有Master、Slave、Sentinel都適應(yīng)。

無(wú)效指令指的是+PONG、-LOADING、-MASTERDOWN之外的其他指令,包括無(wú)響應(yīng)

如果當(dāng)前Sentinel檢測(cè)到Master處于主觀下線狀態(tài),那么它將會(huì)修改其sentinelRedisInstance的flags為SRI_S_DOWN

Redis中主從復(fù)制、Sentinel、集群有什么用

3.2 檢測(cè)Master是否客觀下線

當(dāng)前Sentinel認(rèn)為其下線只能處于主觀下線狀態(tài),要想判斷當(dāng)前Master是否客觀下線,還需要詢(xún)問(wèn)其他Sentinel,并且所有認(rèn)為Master主觀下線或者客觀下線的總和需要達(dá)到quorum配置的值,當(dāng)前Sentinel才會(huì)將Master標(biāo)志為客觀下線。

Redis中主從復(fù)制、Sentinel、集群有什么用

當(dāng)前Sentinel向sentinelRedisInstance實(shí)例中的其他Sentinel發(fā)送如下命令:

SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
  • ip:被判斷為主觀下線的Master的IP地址

  • port:被判斷為主觀下線的Master的端口

  • current_epoch:當(dāng)前sentinel的配置紀(jì)元

  • runid:當(dāng)前sentinel的運(yùn)行id,runid

current_epoch和runid均用于Sentinel的選舉,Master下線之后,需要選舉一個(gè)領(lǐng)頭Sentinel來(lái)選舉一個(gè)新的Master,current_epoch和runid在其中發(fā)揮著重要作用,這個(gè)后續(xù)講解。

接收到命令的Sentinel,會(huì)根據(jù)命令中的參數(shù)檢查主服務(wù)器是否下線,檢查完成后會(huì)返回如下三個(gè)參數(shù):

  • down_state:檢查結(jié)果1代表已下線、0代表未下線

  • leader_runid:返回*代表判斷是否下線,返回runid代表選舉領(lǐng)頭Sentinel

  • leader_epoch:當(dāng)leader_runid返回runid時(shí),配置紀(jì)元會(huì)有值,否則一直返回0

  • 當(dāng)Sentinel檢測(cè)到Master處于主觀下線時(shí),詢(xún)問(wèn)其他Sentinel時(shí)會(huì)發(fā)送current_epoch和runid,此時(shí)current_epoch=0,runid=*

  • 接收到命令的Sentinel返回其判斷Master是否下線時(shí)down_state = 1/0,leader_runid = *,leader_epoch=0

Redis中主從復(fù)制、Sentinel、集群有什么用

3.3 選舉領(lǐng)頭Sentinel

down_state返回1,證明接收is-master-down-by-addr命令的Sentinel認(rèn)為該Master也主觀下線了,如果down_state返回1的數(shù)量(包括本身)大于等于quorum(配置文件中配置的值),那么Master正式被當(dāng)前Sentinel標(biāo)記為客觀下線。 此時(shí),Sentinel會(huì)再次發(fā)送如下指令:

SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

此時(shí)的runid將不再是0,而是Sentinel自己的運(yùn)行id(runid)的值,表示當(dāng)前Sentinel希望接收到is-master-down-by-addr命令的其他Sentinel將其設(shè)置為領(lǐng)頭Sentinel。這個(gè)設(shè)置是先到先得的,Sentinel先接收到誰(shuí)的設(shè)置請(qǐng)求,就將誰(shuí)設(shè)置為領(lǐng)頭Sentinel。 發(fā)送命令的Sentinel會(huì)根據(jù)其他Sentinel回復(fù)的結(jié)果來(lái)判斷自己是否被該Sentinel設(shè)置為領(lǐng)頭Sentinel,如果Sentinel被其他Sentinel設(shè)置為領(lǐng)頭Sentinel的數(shù)量超過(guò)半數(shù)Sentinel(這個(gè)數(shù)量在sentinelRedisInstance的sentinel字典中可以獲取),那么Sentinel會(huì)認(rèn)為自己已經(jīng)成為領(lǐng)頭Sentinel,并開(kāi)始后續(xù)故障轉(zhuǎn)移工作(由于需要半數(shù),且每個(gè)Sentinel只會(huì)設(shè)置一個(gè)領(lǐng)頭Sentinel,那么只會(huì)出現(xiàn)一個(gè)領(lǐng)頭Sentinel,如果沒(méi)有一個(gè)達(dá)到領(lǐng)頭Sentinel的要求,Sentinel將會(huì)重新選舉直到領(lǐng)頭Sentinel產(chǎn)生為止)。

3.4 故障轉(zhuǎn)移

故障轉(zhuǎn)移將會(huì)交給領(lǐng)頭sentinel全權(quán)負(fù)責(zé),領(lǐng)頭sentinel需要做如下事情:

  • 從原先master的slave中,選擇最佳的slave作為新的master

  • 讓其他slave成為新的master的slave

  • 繼續(xù)監(jiān)聽(tīng)舊master,如果其上線,則將其設(shè)置為新的master的slave

這其中最難的一步是如果選擇最佳的新Master,領(lǐng)頭Sentinel會(huì)做如下清洗和排序工作:

  • 判斷slave是否有下線的,如果有從slave列表中移除

  • 刪除5秒內(nèi)未響應(yīng)sentinel的INFO命令的slave

  • 刪除與下線主服務(wù)器斷線時(shí)間超過(guò)down_after_milliseconds * 10 的所有從服務(wù)器

  • 根據(jù)slave優(yōu)先級(jí)slave_priority,選擇優(yōu)先級(jí)最高的slave作為新master

  • 如果優(yōu)先級(jí)相同,根據(jù)slave復(fù)制偏移量slave_repl_offset,選擇偏移量最大的slave作為新master

  • 如果偏移量相同,根據(jù)slave服務(wù)器運(yùn)行id run id排序,選擇run id最小的slave作為新master

新的Master產(chǎn)生后,領(lǐng)頭sentinel會(huì)向已下線主服務(wù)器的其他從服務(wù)器(不包括新Master)發(fā)送SLAVEOF ip port命令,使其成為新master的slave。

到這里Sentinel的的工作流程就算是結(jié)束了,如果新master下線,則循環(huán)流程即可!

三、集群

1、簡(jiǎn)介

Redis集群是Redis提供的分布式數(shù)據(jù)庫(kù)方案,集群通過(guò)分片(sharding)進(jìn)行數(shù)據(jù)共享,Redis集群主要實(shí)現(xiàn)了以下目標(biāo):

  • 在1000個(gè)節(jié)點(diǎn)的時(shí)候仍能表現(xiàn)得很好并且可擴(kuò)展性是線性的。

  • 沒(méi)有合并操作(多個(gè)節(jié)點(diǎn)不存在相同的鍵),這樣在 Redis 的數(shù)據(jù)模型中最典型的大數(shù)據(jù)值中也能有很好的表現(xiàn)。

  • 寫(xiě)入安全,那些與大多數(shù)節(jié)點(diǎn)相連的客戶(hù)端所做的寫(xiě)入操作,系統(tǒng)嘗試全部都保存下來(lái)。但是Redis無(wú)法保證數(shù)據(jù)完全不丟失,異步同步的主從復(fù)制無(wú)論如何都會(huì)存在數(shù)據(jù)丟失的情況。

  • 可用性,主節(jié)點(diǎn)不可用,從節(jié)點(diǎn)能替換主節(jié)點(diǎn)工作。

關(guān)于Redis集群的學(xué)習(xí),如果沒(méi)有任何經(jīng)驗(yàn)的弟兄們建議先看下這三篇文章(中文系列):Redis集群教程

REDIS cluster-tutorial -- Redis中文資料站 -- Redis中國(guó)用戶(hù)組(CRUG)

Redis集群規(guī)范

REDIS cluster-spec -- Redis中文資料站 -- Redis中國(guó)用戶(hù)組(CRUG)

Redis3主3從偽集群部署

CentOS 7單機(jī)安裝Redis Cluster(3主3從偽集群),僅需簡(jiǎn)單五步_李子捌的博客-CSDN博客

下文內(nèi)容依賴(lài)下圖三主三從結(jié)構(gòu)開(kāi)展:

Redis中主從復(fù)制、Sentinel、集群有什么用

資源清單:

節(jié)點(diǎn)IP槽(slot)范圍
Master[0]192.168.211.107:6319Slots 0 - 5460
Master[1]192.168.211.107:6329Slots 5461 - 10922
Master[2]192.168.211.107:6339Slots 10923 - 16383
Slave[0]192.168.211.107:6369
Slave[1]192.168.211.107:6349
Slave[2]192.168.211.107:6359

Redis中主從復(fù)制、Sentinel、集群有什么用

Redis集群.png

2、集群內(nèi)部

Redis 集群沒(méi)有使用一致性hash, 而是引入了 哈希槽的概念。Redis 集群有16384個(gè)哈希槽,每個(gè)key通過(guò)CRC16校驗(yàn)后對(duì)16384取模來(lái)決定放置哪個(gè)槽,這種結(jié)構(gòu)很容易添加或者刪除節(jié)點(diǎn)。集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash槽,比如上面資源清單的集群有3個(gè)節(jié)點(diǎn),其槽分配如下所示:

  • 節(jié)點(diǎn) Master[0] 包含 0 到 5460 號(hào)哈希槽

  • 節(jié)點(diǎn) Master[1] 包含5461 到 10922 號(hào)哈希槽

  • 節(jié)點(diǎn) Master[2] 包含10923到 16383 號(hào)哈希槽

深入學(xué)習(xí)Redis集群之前,需要了解集群中Redis實(shí)例的內(nèi)部結(jié)構(gòu)。當(dāng)某個(gè)Redis服務(wù)節(jié)點(diǎn)通過(guò)cluster_enabled配置為yes開(kāi)啟集群模式之后,Redis服務(wù)節(jié)點(diǎn)不僅會(huì)繼續(xù)使用單機(jī)模式下的服務(wù)器組件,還會(huì)增加custerState、clusterNode、custerLink等結(jié)構(gòu)用于存儲(chǔ)集群模式下的特殊數(shù)據(jù)。

如下三個(gè)數(shù)據(jù)承載對(duì)象一定要認(rèn)真看,尤其是結(jié)構(gòu)中的注釋?zhuān)赐曛蠹捍篌w上怎么工作的,心里就有數(shù)了,嘿嘿嘿;

2.1 clsuterNode

clsuterNode用于存儲(chǔ)節(jié)點(diǎn)信息,比如節(jié)點(diǎn)的名字、IP地址、端口信息和配置紀(jì)元等等,以下代碼列出部分非常重要的屬性:

typedef struct clsuterNode {

    // 創(chuàng)建時(shí)間
    mstime_t ctime;
    
    // 節(jié)點(diǎn)名字,由40位隨機(jī)16進(jìn)制的字符組成(與sentinel中講的服務(wù)器運(yùn)行id相同)
    char name[REDIS_CLUSTER_NAMELEN];
    
    // 節(jié)點(diǎn)標(biāo)識(shí),可以標(biāo)識(shí)節(jié)點(diǎn)的角色和狀態(tài)
    // 角色 -> 主節(jié)點(diǎn)或從節(jié)點(diǎn) 例如:REDIS_NODE_MASTER(主節(jié)點(diǎn)) REDIS_NODE_SLAVE(從節(jié)點(diǎn))
    // 狀態(tài) -> 在線或下線 例如:REDIS_NODE_PFAIL(疑似下線) REDIS_NODE_FAIL(下線) 
    int flags;
    
    // 節(jié)點(diǎn)配置紀(jì)元,用于故障轉(zhuǎn)移,與sentinel中用法類(lèi)似
    // clusterState中的代表集群的配置紀(jì)元
    unit64_t configEpoch;
    
    // 節(jié)點(diǎn)IP地址
    char ip[REDIS_IP_STR_LEN];
    
    // 節(jié)點(diǎn)端口
    int port;
    
    // 連接節(jié)點(diǎn)的信息
    clusterLink *link;
    
    // 一個(gè)2048字節(jié)的二進(jìn)制位數(shù)組
    // 位數(shù)組索引值可能為0或1
    // 數(shù)組索引i位置值為0,代表節(jié)點(diǎn)不負(fù)責(zé)處理槽i
    // 數(shù)組索引i位置值為1,代表節(jié)點(diǎn)負(fù)責(zé)處理槽i
    unsigned char slots[16384/8];
    
    // 記錄當(dāng)前節(jié)點(diǎn)處理槽的數(shù)量總和
    int numslots;
    
    // 如果當(dāng)前節(jié)點(diǎn)是從節(jié)點(diǎn)
    // 指向當(dāng)前從節(jié)點(diǎn)的主節(jié)點(diǎn)
    struct clusterNode *slaveof;
    
    // 如果當(dāng)前節(jié)點(diǎn)是主節(jié)點(diǎn)
    // 正在復(fù)制當(dāng)前主節(jié)點(diǎn)的從節(jié)點(diǎn)數(shù)量
    int numslaves;
    
    // 數(shù)組——記錄正在復(fù)制當(dāng)前主節(jié)點(diǎn)的所有從節(jié)點(diǎn)
    struct clusterNode **slaves;
    
} clsuterNode;

上述代碼中可能不太好理解的是slots[16384/8],其實(shí)可以簡(jiǎn)單的理解為一個(gè)16384大小的數(shù)組,數(shù)組索引下標(biāo)處如果為1表示當(dāng)前槽屬于當(dāng)前clusterNode處理,如果為0表示不屬于當(dāng)前clusterNode處理。clusterNode能夠通過(guò)slots來(lái)識(shí)別,當(dāng)前節(jié)點(diǎn)處理負(fù)責(zé)處理哪些槽。 初始clsuterNode或者未分配槽的集群中的clsuterNode的slots如下所示:

Redis中主從復(fù)制、Sentinel、集群有什么用

假設(shè)集群如上面我給出的資源清單,此時(shí)代表Master[0]的clusterNode的slots如下所示:

Redis中主從復(fù)制、Sentinel、集群有什么用

2.2 clusterLink

clusterLink是clsuterNode中的一個(gè)屬性,用于存儲(chǔ)連接節(jié)點(diǎn)所需的相關(guān)信息,比如套接字描述符、輸入輸出緩沖區(qū)等待,以下代碼列出部分非常重要的屬性:

typedef struct clusterState {

    // 連接創(chuàng)建時(shí)間
    mstime_t ctime;
   
    // TCP 套接字描述符
    int fd;
    
    // 輸出緩沖區(qū),需要發(fā)送給其他節(jié)點(diǎn)的消息緩存在這里
    sds sndbuf;
    
    // 輸入緩沖區(qū),接收打其他節(jié)點(diǎn)的消息緩存在這里
    sds rcvbuf;
    
    // 與當(dāng)前clsuterNode節(jié)點(diǎn)代表的節(jié)點(diǎn)建立連接的其他節(jié)點(diǎn)保存在這里
    struct clusterNode *node;
} clusterState;

2.3 custerState

每個(gè)節(jié)點(diǎn)都會(huì)有一個(gè)custerState結(jié)構(gòu),這個(gè)結(jié)構(gòu)中存儲(chǔ)了當(dāng)前集群的全部數(shù)據(jù),比如集群狀態(tài)、集群中的所有節(jié)點(diǎn)信息(主節(jié)點(diǎn)、從節(jié)點(diǎn))等等,以下代碼列出部分非常重要的屬性:

typedef struct clusterState {

    // 當(dāng)前節(jié)點(diǎn)指針,指向一個(gè)clusterNode
    clusterNode *myself;
    
    // 集群當(dāng)前配置紀(jì)元,用于故障轉(zhuǎn)移,與sentinel中用法類(lèi)似
    unit64_t currentEpoch;
    
    // 集群狀態(tài) 在線/下線
    int state;
    
    // 集群中處理著槽的節(jié)點(diǎn)數(shù)量總和
    int size;
    
    // 集群節(jié)點(diǎn)字典,所有clusterNode包括自己
    dict *node;
    
    // 集群中所有槽的指派信息
    clsuterNode *slots[16384];
    
    // 用于槽的重新分配——記錄當(dāng)前節(jié)點(diǎn)正在從其他節(jié)點(diǎn)導(dǎo)入的槽
    clusterNode *importing_slots_from[16384];
    
    // 用于槽的重新分配——記錄當(dāng)前節(jié)點(diǎn)正在遷移至其他節(jié)點(diǎn)的槽
    clusterNode *migrating_slots_to[16384];
    
    // ...
    
} clusterState;

在custerState有三個(gè)結(jié)構(gòu)需要認(rèn)真了解的,第一個(gè)是slots數(shù)組,clusterState中的slots數(shù)組與clsuterNode中的slots數(shù)組是不一樣的,在clusterNode中slots數(shù)組記錄的是當(dāng)前clusterNode所負(fù)責(zé)的槽,而clusterState中的slots數(shù)組記錄的是整個(gè)集群的每個(gè)槽由哪個(gè)clsuterNode負(fù)責(zé),因此集群正常工作的時(shí)候clusterState的slots數(shù)組每個(gè)索引指向負(fù)責(zé)該槽的clusterNode,集群槽未分配之前指向null。

如圖展示資源清單中的集群clusterState中的slots數(shù)組與clsuterNode中的slots數(shù)組:

Redis中主從復(fù)制、Sentinel、集群有什么用

Redis集群中使用兩個(gè)slots數(shù)組的原因是出于性能的考慮:

  • 當(dāng)我們需要獲取整個(gè)集群中clusterNode分別負(fù)責(zé)什么槽時(shí),只需要查詢(xún)clusterState中的slots數(shù)組即可。如果沒(méi)有clusterState的slots數(shù)組,則需要遍歷所有的clusterNode結(jié)構(gòu),這樣顯然要慢一些

  • 此外clusterNode中的slots數(shù)組也有存在的必要,因?yàn)榧褐腥我庖粋€(gè)節(jié)點(diǎn)之間需要知道彼此負(fù)責(zé)的槽,此時(shí)節(jié)點(diǎn)之間只需要互相傳輸clusterNode中的slots數(shù)組結(jié)構(gòu)就行。

第二個(gè)需要認(rèn)真了解的結(jié)構(gòu)是node字典,該結(jié)構(gòu)雖然簡(jiǎn)單,但是node字典中存儲(chǔ)了所有的clusterNode,這也是Redis集群中的單個(gè)節(jié)點(diǎn)獲取其他主節(jié)點(diǎn)、從節(jié)點(diǎn)信息的主要位置,因此我們也需要注意一下。 第三個(gè)需要認(rèn)真了解的結(jié)構(gòu)是importing_slots_from[16384]數(shù)組和migrating_slots_to[16384],這兩個(gè)數(shù)組在集群重新分片時(shí)需要使用,需要重點(diǎn)了解,后面再說(shuō)吧,這里說(shuō)的話(huà)順序不太對(duì)。

3、集群工作

3.1 槽(slot)如何指派?

Redis集群一共16384個(gè)槽,如上資源清單我們?cè)谌魅龔牡募褐校總€(gè)主節(jié)點(diǎn)負(fù)責(zé)自己相應(yīng)的槽,而在上面的三主三從部署的過(guò)程中并未看到我指定槽給對(duì)應(yīng)的主節(jié)點(diǎn),這是因?yàn)镽edis集群自己內(nèi)部給我們劃分了槽,但是如果我們想自己指派槽該如何整呢? 我們可以向節(jié)點(diǎn)發(fā)送如下命令,將一個(gè)或多個(gè)槽指派給當(dāng)前節(jié)點(diǎn)負(fù)責(zé):

CLUSTER ADDSLOTS

比如我們想把0和1槽指派給Master[0],我們只需要想Master[0]節(jié)點(diǎn)發(fā)送如下命令即可:

CLUSTER ADDSLOTS 0 1

當(dāng)節(jié)點(diǎn)被指派了槽后,會(huì)將clusterNode的slots數(shù)組更新,節(jié)點(diǎn)會(huì)將自己負(fù)責(zé)處理的槽也就是slots數(shù)組通過(guò)消息發(fā)送給集群中的其他節(jié)點(diǎn),其他節(jié)點(diǎn)在接收當(dāng)消息后會(huì)更新對(duì)應(yīng)clusterNode的slots數(shù)組以及clusterState的solts數(shù)組。

3.2 ADDSLOTS 在Redis集群內(nèi)部是如何實(shí)現(xiàn)的呢?

這個(gè)其實(shí)也比較簡(jiǎn)單,當(dāng)我們向Redis集群中的某個(gè)節(jié)點(diǎn)發(fā)送CLUSTER ADDSLOTS命令時(shí),當(dāng)前節(jié)點(diǎn)首先會(huì)通過(guò)clusterState中的slots數(shù)組來(lái)確認(rèn)指派給當(dāng)前節(jié)點(diǎn)的槽是否沒(méi)有指派給其他節(jié)點(diǎn),如果已經(jīng)指派了,那么會(huì)直接拋出異常,返回錯(cuò)誤給指派的客戶(hù)端。如果指派給當(dāng)前節(jié)點(diǎn)的所有槽都未指派給其他節(jié)點(diǎn),那么當(dāng)前節(jié)點(diǎn)會(huì)將這些槽指派給自己。 指派主要有三個(gè)步驟:

  • 更新clusterState的slots數(shù)組,將指定槽slots[i]指向當(dāng)前clusterNode

  • 更新clusterNode的slots數(shù)組,將指定槽slots[i]處的值更新為1

  • 向集群中的其他節(jié)點(diǎn)發(fā)送消息,將clusterNode的slots數(shù)組發(fā)送給其他節(jié)點(diǎn),其他節(jié)點(diǎn)接收到消息后也更新對(duì)應(yīng)的clusterState的slots數(shù)組和clusterNode的slots數(shù)組

3.3 集群這么多節(jié)點(diǎn),客戶(hù)端怎么知道請(qǐng)求哪個(gè)節(jié)點(diǎn)?

在了解這個(gè)問(wèn)題之前先要知道一個(gè)點(diǎn),Redis集群是怎么計(jì)算當(dāng)前這個(gè)鍵屬于哪個(gè)槽的呢?根據(jù)官網(wǎng)的介紹,Redis其實(shí)并未使用一致性hash算法,而是將每個(gè)請(qǐng)求的key通過(guò)CRC16校驗(yàn)后對(duì)16384取模來(lái)決定放置到哪個(gè)槽中。

HASH_SLOT = CRC16(key) mod 16384

此時(shí),當(dāng)客戶(hù)端連接向某個(gè)節(jié)點(diǎn)發(fā)送請(qǐng)求時(shí),當(dāng)前接收到命令的節(jié)點(diǎn)首先會(huì)通過(guò)算法計(jì)算出當(dāng)前key所屬的槽i,計(jì)算完后當(dāng)前節(jié)點(diǎn)會(huì)判斷clusterState的槽i是否由自己負(fù)責(zé),如果恰好由自己負(fù)責(zé)那么當(dāng)前節(jié)點(diǎn)就會(huì)之間響應(yīng)客戶(hù)端的請(qǐng)求,如果不由當(dāng)前節(jié)點(diǎn)負(fù)責(zé),則會(huì)經(jīng)歷如下步驟:

  • 節(jié)點(diǎn)向客戶(hù)端返回MOVED重定向錯(cuò)誤,MOVED重定向錯(cuò)誤中會(huì)將計(jì)算好的正確處理該key的clusterNode的ip和port返回給客戶(hù)端

  • 客戶(hù)端接收到節(jié)點(diǎn)返回的MOVED重定向錯(cuò)誤時(shí),會(huì)根據(jù)ip和port將命令轉(zhuǎn)發(fā)給正確的節(jié)點(diǎn),整個(gè)處理過(guò)程對(duì)程序員來(lái)說(shuō)透明,由Redis集群的服務(wù)端和客戶(hù)端共同負(fù)責(zé)完成。

3.4 如果我想將已經(jīng)分配給A節(jié)點(diǎn)的槽重新分配給B節(jié)點(diǎn),怎么整?

這個(gè)問(wèn)題其實(shí)涵括了很多問(wèn)題,比如移除Redis集群中的某些節(jié)點(diǎn),增加節(jié)點(diǎn)等都可以概括為把哈希槽從一個(gè)節(jié)點(diǎn)移動(dòng)到另外一個(gè)節(jié)點(diǎn)。并且Redis集群非常牛逼的一點(diǎn)也在這里,它支持在線(不停機(jī))的分配,也就是官方說(shuō)集群在線重配置(live reconfiguration )。

在將實(shí)現(xiàn)之前先來(lái)看下CLUSTER的指令,指令會(huì)了操作就會(huì)了:

  • CLUSTER ADDSLOTS slot1 [slot2] … [slotN]

  • CLUSTER DELSLOTS slot1 [slot2] … [slotN]

  • CLUSTER SETSLOT slot NODE node

  • CLUSTER SETSLOT slot MIGRATING node

  • CLUSTER SETSLOT slot IMPORTING node

CLUSTER 用于槽分配的指令主要有如上這些,ADDSLOTS 和DELSLOTS主要用于槽的快速指派和快速刪除,通常我們?cè)诩簞倓偨⒌臅r(shí)候進(jìn)行快速分配的時(shí)候才使用。CLUSTER SETSLOT slot NODE node也用于直接給指定的節(jié)點(diǎn)指派槽。如果集群已經(jīng)建立我們通常使用最后兩個(gè)來(lái)重分配,其代表的含義如下所示:

  • 當(dāng)一個(gè)槽被設(shè)置為 MIGRATING,原來(lái)持有該哈希槽的節(jié)點(diǎn)仍會(huì)接受所有跟這個(gè)哈希槽有關(guān)的請(qǐng)求,但只有當(dāng)查詢(xún)的鍵還存在原節(jié)點(diǎn)時(shí),原節(jié)點(diǎn)會(huì)處理該請(qǐng)求,否則這個(gè)查詢(xún)會(huì)通過(guò)一個(gè) -ASK 重定向(-ASK redirection)轉(zhuǎn)發(fā)到遷移的目標(biāo)節(jié)點(diǎn)。

  • 當(dāng)一個(gè)槽被設(shè)置為 IMPORTING,只有在接受到 ASKING 命令之后節(jié)點(diǎn)才會(huì)接受所有查詢(xún)這個(gè)哈希槽的請(qǐng)求。如果客戶(hù)端一直沒(méi)有發(fā)送 ASKING 命令,那么查詢(xún)都會(huì)通過(guò) -MOVED 重定向錯(cuò)誤轉(zhuǎn)發(fā)到真正處理這個(gè)哈希槽的節(jié)點(diǎn)那里。

上面這兩句話(huà)是不是感覺(jué)不太看的懂,這是官方的描述,不太懂的話(huà)我來(lái)給你通俗的描述,整個(gè)流程大致如下步驟:

  • redis-trib(集群管理軟件redis-trib會(huì)負(fù)責(zé)Redis集群的槽分配工作),向目標(biāo)節(jié)點(diǎn)(槽導(dǎo)入節(jié)點(diǎn))發(fā)送CLUSTER SETSLOT slot IMPORTING node命令,目標(biāo)節(jié)點(diǎn)會(huì)做好從源節(jié)點(diǎn)(槽導(dǎo)出節(jié)點(diǎn))導(dǎo)入槽的準(zhǔn)備工作。

  • redis-trib隨即向源節(jié)點(diǎn)發(fā)送CLUSTER SETSLOT slot MIGRATING node命令,源節(jié)點(diǎn)會(huì)做好槽導(dǎo)出準(zhǔn)備工作

  • redis-trib隨即向源節(jié)點(diǎn)發(fā)送CLUSTER GETKEYSINSLOT slot count命令,源節(jié)點(diǎn)接收命令后會(huì)返回屬于槽slot的鍵,最多返回count個(gè)鍵

  • redis-trib會(huì)根據(jù)源節(jié)點(diǎn)返回的鍵向源節(jié)點(diǎn)依次發(fā)送MIGRATE ip port key 0 timeout命令,如果key在源節(jié)點(diǎn)中,將會(huì)遷移至目標(biāo)節(jié)點(diǎn)。

  • 遷移完成之后,redis-trib會(huì)向集群中的某個(gè)節(jié)點(diǎn)發(fā)送CLUSTER SETSLOT slot NODE node命令,節(jié)點(diǎn)接收到命令后會(huì)更新clusterNode和clusterState結(jié)構(gòu),然后節(jié)點(diǎn)通過(guò)消息傳播槽的指派信息,至此集群槽遷移工作完成,且集群中的其他節(jié)點(diǎn)也更新了新的槽分配信息。

3.5 如果客戶(hù)端訪問(wèn)的key所屬的槽正在遷移怎么辦?

優(yōu)秀的你總會(huì)想到這種并發(fā)情況,牛皮呀!大佬們!

Redis中主從復(fù)制、Sentinel、集群有什么用

這個(gè)問(wèn)題官方也考慮了,還記得我們?cè)诹腸lusterState結(jié)構(gòu)的時(shí)候么?importing_slots_from和migrating_slots_to就是用來(lái)處理這個(gè)問(wèn)題的。

typedef struct clusterState {

    // ...
    
    // 用于槽的重新分配——記錄當(dāng)前節(jié)點(diǎn)正在從其他節(jié)點(diǎn)導(dǎo)入的槽
    clusterNode *importing_slots_from[16384];
    
    // 用于槽的重新分配——記錄當(dāng)前節(jié)點(diǎn)正在遷移至其他節(jié)點(diǎn)的槽
    clusterNode *migrating_slots_to[16384];
    
    // ...
    
} clusterState;
  • 當(dāng)節(jié)點(diǎn)正在導(dǎo)出某個(gè)槽,則會(huì)在clusterState中的migrating_slots_to數(shù)組對(duì)應(yīng)的下標(biāo)處設(shè)置其指向?qū)?yīng)的clusterNode,這個(gè)clusterNode會(huì)指向?qū)氲墓?jié)點(diǎn)。

  • 當(dāng)節(jié)點(diǎn)正在導(dǎo)入某個(gè)槽,則會(huì)在clusterState中的importing_slots_from數(shù)組對(duì)應(yīng)的下標(biāo)處設(shè)置其指向?qū)?yīng)的clusterNode,這個(gè)clusterNode會(huì)指向?qū)С龅墓?jié)點(diǎn)。

有了上述兩個(gè)相互數(shù)組,就能判斷當(dāng)前槽是否在遷移了,而且從哪里遷移來(lái),要遷移到哪里去?搞笑不就是這么簡(jiǎn)單……

此時(shí),回到問(wèn)題中,如果客戶(hù)端請(qǐng)求的key剛好屬于正在遷移的槽。那么接收到命令的節(jié)點(diǎn)首先會(huì)嘗試在自己的數(shù)據(jù)庫(kù)中查找鍵key,如果這個(gè)槽還沒(méi)遷移完成,且當(dāng)前key剛好也還沒(méi)遷移完成,那就直接響應(yīng)客戶(hù)端的請(qǐng)求就行。如果該key已經(jīng)不在了,此時(shí)節(jié)點(diǎn)會(huì)去查詢(xún)migrating_slots_to數(shù)組對(duì)應(yīng)的索引槽,如果索引處的值不為null,而是指向了某個(gè)clusterNode結(jié)構(gòu),那說(shuō)明這個(gè)key已經(jīng)被遷移到這個(gè)clusterNode了。這個(gè)時(shí)候節(jié)點(diǎn)不會(huì)繼續(xù)在處理指令,而是返回ASKING命令,這個(gè)命令也會(huì)攜帶導(dǎo)入槽clusterNode對(duì)應(yīng)的ip和port??蛻?hù)端在接收到ASKING命令之后就需要將請(qǐng)求轉(zhuǎn)向正確的節(jié)點(diǎn)了,不過(guò)這里有一點(diǎn)需要注意的地方**(因此我放個(gè)表情包在這里,方便讀者注意)。**

Redis中主從復(fù)制、Sentinel、集群有什么用

前面說(shuō)了,當(dāng)節(jié)點(diǎn)發(fā)現(xiàn)當(dāng)前槽不屬于自己處理時(shí)會(huì)返回MOVED指令,那么在遷移中的槽時(shí)怎么處理的呢?這個(gè)Redis集群是這個(gè)玩的。 節(jié)點(diǎn)發(fā)現(xiàn)槽正在遷移則向客戶(hù)端返回ASKING命令,客戶(hù)端會(huì)接收到ASKING命令,其中包含了槽遷入的clusterNode的節(jié)點(diǎn)ip和port。那么客戶(hù)端首先會(huì)向遷入的clusterNode發(fā)送一條ASKING命令,這個(gè)命令必須要發(fā)目的是告訴當(dāng)前節(jié)點(diǎn),你要破例處理這次請(qǐng)求,因?yàn)檫@個(gè)槽已經(jīng)遷移到你這里了,你不能直接拒絕我(因此如果Redis未接收到ASKING命令,會(huì)直接查詢(xún)節(jié)點(diǎn)的clusterState,而正在遷移中的槽還沒(méi)有更新到clusterState中,那么只能直接返回MOVED,這樣不就會(huì)一直循環(huán)很多次……),接收到ASKING命令的節(jié)點(diǎn)會(huì)強(qiáng)制執(zhí)行一次這個(gè)請(qǐng)求(只執(zhí)行一次,下次再來(lái)需要重新提前發(fā)送ASKING命令)。

4、集群故障

Redis集群故障比較簡(jiǎn)單,這個(gè)和sentinel中主節(jié)點(diǎn)宕機(jī)或者在指定最長(zhǎng)時(shí)間內(nèi)未響應(yīng),重新在從節(jié)點(diǎn)中選舉新的主節(jié)點(diǎn)的方式其實(shí)差不多。當(dāng)然前提是Redis集群中的每個(gè)主節(jié)點(diǎn),我們提前設(shè)置了從節(jié)點(diǎn),要不就嘿嘿嘿……沒(méi)戲。其大致步驟如下:

  • 正常工作的集群,每個(gè)節(jié)點(diǎn)之間會(huì)定期向其他節(jié)點(diǎn)發(fā)送PING命令,如果接收命令的節(jié)點(diǎn)未在規(guī)定時(shí)間內(nèi)返回PONG消息 ,當(dāng)前節(jié)點(diǎn)會(huì)將接收命令的節(jié)點(diǎn)的clusterNode的flags設(shè)置為REDIS_NODE_PFAIL,PFAIL并不是下線,而是疑似下線。

  • 集群節(jié)點(diǎn)會(huì)通過(guò)發(fā)送消息的方式來(lái)告知其他節(jié)點(diǎn),集群中各個(gè)節(jié)點(diǎn)的狀態(tài)信息

  • 如果集群中半數(shù)以上負(fù)責(zé)處理槽的主節(jié)點(diǎn)都將某個(gè)主節(jié)點(diǎn)設(shè)置為疑似下線,那么這個(gè)節(jié)點(diǎn)將會(huì)被標(biāo)記位下線狀態(tài),節(jié)點(diǎn)會(huì)將接收命令的節(jié)點(diǎn)的clusterNode的flags設(shè)置為REDIS_NODE_FAIL,F(xiàn)AIL表示已下線

  • 集群節(jié)點(diǎn)通過(guò)發(fā)送消息的方式來(lái)告知其他節(jié)點(diǎn),集群中各個(gè)節(jié)點(diǎn)的狀態(tài)信息,此時(shí)下線節(jié)點(diǎn)的從節(jié)點(diǎn)在發(fā)現(xiàn)自己的主節(jié)點(diǎn)已經(jīng)被標(biāo)記為下線狀態(tài)了,那么是時(shí)候挺身而出了

  • 下線主節(jié)點(diǎn)的從節(jié)點(diǎn),會(huì)選舉出一個(gè)從節(jié)點(diǎn)作為最新的主節(jié)點(diǎn),執(zhí)行被選中的節(jié)點(diǎn)指向SLAVEOF no one成為新的主節(jié)點(diǎn)

  • 新的主節(jié)點(diǎn)會(huì)撤銷(xiāo)掉原主節(jié)點(diǎn)的槽指派,并將這些槽指派修改為自己,也就是修改clusterNode結(jié)構(gòu)和clusterState結(jié)構(gòu)

  • 新的主節(jié)點(diǎn)向集群廣播一條PONG指令,其他節(jié)點(diǎn)將會(huì)知道有新的主節(jié)點(diǎn)產(chǎn)生,并更新clusterNode結(jié)構(gòu)和clusterState結(jié)構(gòu)

  • 新的主節(jié)點(diǎn)如果會(huì)向原主節(jié)點(diǎn)剩余的從節(jié)點(diǎn)發(fā)送新的SLAVEOF指令,使其成為自己的從節(jié)點(diǎn)

  • 最后新的主節(jié)點(diǎn)將會(huì)負(fù)責(zé)原主節(jié)點(diǎn)的槽的響應(yīng)工作

以上是“Redis中主從復(fù)制、Sentinel、集群有什么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

網(wǎng)站標(biāo)題:Redis中主從復(fù)制、Sentinel、集群有什么用
分享URL:http://muchs.cn/article8/iidcop.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)、網(wǎng)站維護(hù)、手機(jī)網(wǎng)站建設(shè)、企業(yè)網(wǎng)站制作關(guān)鍵詞優(yōu)化、全網(wǎng)營(yíng)銷(xiāo)推廣

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)