溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2021-10-15 11:48:38 來源:億速云 閱讀:151 作者:小新 欄目:關(guān)系型數(shù)據(jù)庫

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

一、主從復(fù)制

1、簡介

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

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

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

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

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

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

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

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

  • 2.8以前

  • 2.8~4.0

  • 4.0以后

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

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

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

2.1 版本2.8以前

2.1.1 同步

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

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

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

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

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

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

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

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

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

2.1.2 命令傳播

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

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

2.1.3 缺陷

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

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

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

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

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

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

2.2 版本2.8-4.0

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

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

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

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

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

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

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

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

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

2.2.2.1 復(fù)制偏移量

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

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

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

正常同步的情況如下:

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

通過對比主從服務(wù)器之間的復(fù)制偏移量是否相等,能夠得知主從服務(wù)器之間的數(shù)據(jù)狀態(tài)是否保持一致。 假設(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ū)是一個固定長度的隊(duì)列,默認(rèn)為1MB大小。當(dāng)主服務(wù)器數(shù)據(jù)狀態(tài)發(fā)生改變,主服務(wù)器將數(shù)據(jù)同步給從服務(wù)器的同時會另存一份到復(fù)制積壓緩沖區(qū)中。

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

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

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

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

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

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

Redis的復(fù)制積壓緩沖區(qū)的大小默認(rèn)為1MB,如果需要自定義應(yīng)該如何設(shè)置呢? 很明顯,我們希望能盡可能的使用增量同步,但是又不希望緩沖區(qū)占用過多的內(nèi)存空間。那么我們可以通過預(yù)估Redis從服務(wù)斷線后重連的時間T,Redis主服務(wù)器每秒接收的寫命令的內(nèi)存大小M,來設(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í)還有一種情況沒考慮,就是當(dāng)主服務(wù)器宕機(jī)后,某臺從服務(wù)器被選舉成為新的主服務(wù)器,這種情況我們就通過比較運(yùn)行ID來區(qū)分。

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

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

  • 當(dāng)從服務(wù)器斷線重連后,從服務(wù)器會向主服務(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過程非常的復(fù)雜,在2.8-4.0的主從復(fù)制版本中已經(jīng)做到了非常完善。psync命令發(fā)送的參數(shù)如下:

psync

當(dāng)從服務(wù)器沒有復(fù)制過任何主服務(wù)器(并不是主從第一次復(fù)制,因?yàn)橹鞣?wù)器可能會變化,而是從服務(wù)器第一次全量同步),從服務(wù)器將會發(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主動向主服務(wù)器發(fā)起套接字連接,同時從服務(wù)將會未這個套接字連接關(guān)聯(lián)一個專門用于文件復(fù)制工作的文件事件處理器,用于后續(xù)的RDB文件復(fù)制等工作

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

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

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

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

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

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

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

2.3 版本4.0

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

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

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

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

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

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

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

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

二、Sentinel

1、簡介

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

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

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

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

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

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

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

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并沒有什么特別神奇的地方,它就是一個更加簡單的Redis服務(wù)器,在Sentinel啟動的時候它會加載不同的命令表和配置文件,因此從本質(zhì)上來講Sentinel就是一個擁有較少命令和部分特殊功能的Redis服務(wù)。當(dāng)一個Sentinel啟動時它需要經(jīng)歷如下步驟:

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

  • 替換普通Redis代碼為Sentinel的專用代碼

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

  • 根據(jù)用戶給定的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ì)上就是一個Redis服務(wù)器,因此啟動Sentinel需要啟動一個Redis服務(wù)器,但是Sentinel并不需要讀取RDB/AOF文件來還原數(shù)據(jù)狀態(tài)。

2.2 替換普通Redis代碼為Sentinel的專用代碼

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

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

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

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

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

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

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

  • masters的value是一個指向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信息都保存在這個實(shí)例中)。

typedef struct sentinelRedisInstance {
 
    // 標(biāo)識值,標(biāo)識當(dāng)前實(shí)例的類型和狀態(tài)。如SRI_MASTER、SRI_SLVAE、SRI_SENTINEL
    int flags;
    
    // 實(shí)例名稱 主服務(wù)器為用戶配置實(shí)例名稱、從服務(wù)器和Sentinel為ip:port
    char *name;
    
    // 服務(wù)器運(yùn)行ID
    char *runid;
    
    //配置紀(jì)元,故障轉(zhuǎn)移使用
 uint64_t config_epoch; 
    
    // 實(shí)例地址
    sentinelAddr *addr;
    
    // 實(shí)例判斷為主觀下線的時長 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)移操作時,可以同時對新的主服務(wù)器進(jìn)行同步的從服務(wù)器數(shù)量 sentinel parallel-syncs redis-master 1
    int parallel-syncs;
    
    // 刷新故障遷移狀態(tài)的最大時限 sentinel failover-timeout redis-master 180000
 mstime_t failover_timeout;
    
    // ...
} sentinelRedisInstance;

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

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

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

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

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

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

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

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

  • 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之間也會創(chuàng)建命令連接和訂閱連接。

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

當(dāng)Sentinel和Slave之間創(chuàng)建網(wǎng)絡(luò)連接之后,Sentinel成為了Slave的客戶端,Sentinel也會每隔10秒鐘通過INFO指令請求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)先級slave_priority

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

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

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

此時是不是還有疑問,Sentinel之間是怎么互相發(fā)現(xiàn)對方并且相互通信的,這個就和上面Sentinel與自己監(jiān)視的主從之間訂閱_sentinel_:hello頻道有關(guān)了。 Sentinel會與自己監(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ì)元。

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

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

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

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

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

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

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

3、Sentinel工作

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

3.1 檢測Master是否主觀下線

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

sentinel down-after-milliseconds redis-master 30000

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

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

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

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

3.2 檢測Master是否客觀下線

當(dāng)前Sentinel認(rèn)為其下線只能處于主觀下線狀態(tài),要想判斷當(dāng)前Master是否客觀下線,還需要詢問其他Sentinel,并且所有認(rèn)為Master主觀下線或者客觀下線的總和需要達(dá)到quorum配置的值,當(dāng)前Sentinel才會將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下線之后,需要選舉一個領(lǐng)頭Sentinel來選舉一個新的Master,current_epoch和runid在其中發(fā)揮著重要作用,這個后續(xù)講解。

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

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

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

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

  • 當(dāng)Sentinel檢測到Master處于主觀下線時,詢問其他Sentinel時會發(fā)送current_epoch和runid,此時current_epoch=0,runid=*

  • 接收到命令的Sentinel返回其判斷Master是否下線時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)記為客觀下線。 此時,Sentinel會再次發(fā)送如下指令:

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

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

3.4 故障轉(zhuǎn)移

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

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

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

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

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

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

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

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

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

  • 如果優(yōu)先級相同,根據(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會向已下線主服務(wù)器的其他從服務(wù)器(不包括新Master)發(fā)送SLAVEOF ip port命令,使其成為新master的slave。

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

三、集群

1、簡介

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

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

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

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

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

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

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

Redis集群規(guī)范

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

Redis3主3從偽集群部署

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

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

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

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

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

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

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

如下三個數(shù)據(jù)承載對象一定要認(rèn)真看,尤其是結(jié)構(gòu)中的注釋,看完之后集群大體上怎么工作的,心里就有數(shù)了,嘿嘿嘿;

2.1 clsuterNode

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

typedef struct clsuterNode {

    // 創(chuàng)建時間
    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)識,可以標(biāo)識節(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中用法類似
    // 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;
    
    // 一個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í)可以簡單的理解為一個16384大小的數(shù)組,數(shù)組索引下標(biāo)處如果為1表示當(dāng)前槽屬于當(dāng)前clusterNode處理,如果為0表示不屬于當(dāng)前clusterNode處理。clusterNode能夠通過slots來識別,當(dāng)前節(jié)點(diǎn)處理負(fù)責(zé)處理哪些槽。 初始clsuterNode或者未分配槽的集群中的clsuterNode的slots如下所示:

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

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

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

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

typedef struct clusterState {

    // 連接創(chuàng)建時間
    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

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

typedef struct clusterState {

    // 當(dāng)前節(jié)點(diǎn)指針,指向一個clusterNode
    clusterNode *myself;
    
    // 集群當(dāng)前配置紀(jì)元,用于故障轉(zhuǎn)移,與sentinel中用法類似
    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有三個結(jié)構(gòu)需要認(rèn)真了解的,第一個是slots數(shù)組,clusterState中的slots數(shù)組與clsuterNode中的slots數(shù)組是不一樣的,在clusterNode中slots數(shù)組記錄的是當(dāng)前clusterNode所負(fù)責(zé)的槽,而clusterState中的slots數(shù)組記錄的是整個集群的每個槽由哪個clsuterNode負(fù)責(zé),因此集群正常工作的時候clusterState的slots數(shù)組每個索引指向負(fù)責(zé)該槽的clusterNode,集群槽未分配之前指向null。

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

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

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

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

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

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

3、集群工作

3.1 槽(slot)如何指派?

Redis集群一共16384個槽,如上資源清單我們在三主三從的集群中,每個主節(jié)點(diǎn)負(fù)責(zé)自己相應(yīng)的槽,而在上面的三主三從部署的過程中并未看到我指定槽給對應(yīng)的主節(jié)點(diǎn),這是因?yàn)镽edis集群自己內(nèi)部給我們劃分了槽,但是如果我們想自己指派槽該如何整呢? 我們可以向節(jié)點(diǎn)發(fā)送如下命令,將一個或多個槽指派給當(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)被指派了槽后,會將clusterNode的slots數(shù)組更新,節(jié)點(diǎn)會將自己負(fù)責(zé)處理的槽也就是slots數(shù)組通過消息發(fā)送給集群中的其他節(jié)點(diǎn),其他節(jié)點(diǎn)在接收當(dāng)消息后會更新對應(yīng)clusterNode的slots數(shù)組以及clusterState的solts數(shù)組。

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

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

  • 更新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)接收到消息后也更新對應(yīng)的clusterState的slots數(shù)組和clusterNode的slots數(shù)組

3.3 集群這么多節(jié)點(diǎn),客戶端怎么知道請求哪個節(jié)點(diǎn)?

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

HASH_SLOT = CRC16(key) mod 16384

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

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

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

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

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

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

  • 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主要用于槽的快速指派和快速刪除,通常我們在集群剛剛建立的時候進(jìn)行快速分配的時候才使用。CLUSTER SETSLOT slot NODE node也用于直接給指定的節(jié)點(diǎn)指派槽。如果集群已經(jīng)建立我們通常使用最后兩個來重分配,其代表的含義如下所示:

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

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

上面這兩句話是不是感覺不太看的懂,這是官方的描述,不太懂的話我來給你通俗的描述,整個流程大致如下步驟:

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

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

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

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

3.5 如果客戶端訪問的key所屬的槽正在遷移怎么辦?

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

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

這個問題官方也考慮了,還記得我們在聊clusterState結(jié)構(gòu)的時候么?importing_slots_from和migrating_slots_to就是用來處理這個問題的。

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)出某個槽,則會在clusterState中的migrating_slots_to數(shù)組對應(yīng)的下標(biāo)處設(shè)置其指向?qū)?yīng)的clusterNode,這個clusterNode會指向?qū)氲墓?jié)點(diǎn)。

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

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

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

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

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

4、集群故障

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

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

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

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

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

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

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

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

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

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

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

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI