您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Redis中主從復(fù)制、Sentinel、集群有什么用”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Redis中主從復(fù)制、Sentinel、集群有什么用”這篇文章吧。
主從復(fù)制是Redis分布式的基石,也是Redis高可用的保障。在Redis中,被復(fù)制的服務(wù)器稱為主服務(wù)器(Master),對主服務(wù)器進(jìn)行復(fù)制的服務(wù)器稱為從服務(wù)器(Slave)。
主從復(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
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.1 同步
2.8以前的版本,從服務(wù)器對主服務(wù)器的同步需要從服務(wù)器向主服務(wù)器發(fā)生sync命令來完成:
從服務(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)將會保持一致。
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.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
正常同步的情況如下:
通過對比主從服務(wù)器之間的復(fù)制偏移量是否相等,能夠得知主從服務(wù)器之間的數(shù)據(jù)狀態(tài)是否保持一致。 假設(shè)此時A/B正常傳播,C從服務(wù)器斷線,那么將出現(xiàn)如下情況:
很明顯有了復(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ū)中。
復(fù)制積壓緩沖區(qū)為了能和偏移量進(jìn)行匹配,它不僅存儲了數(shù)據(jù)內(nèi)容,還記錄了每個字節(jié)對應(yīng)的偏移量:
當(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
一起完整的psync流程如下圖:
從服務(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ū)中)
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)行全量同步。
主從復(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
本文基于如下資源清單進(jìn)行開展:
IP地址 | 節(jié)點(diǎn)角色 | 端口 |
---|---|---|
192.168.211.104 | Redis Master/ Sentinel | 6379/26379 |
192.168.211.105 | Redis Slave/ Sentinel | 6379/26379 |
192.168.211.106 | Redis Slave/ Sentinel | 6379/26379 |
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ò)連接
Sentinel本質(zhì)上就是一個Redis服務(wù)器,因此啟動Sentinel需要啟動一個Redis服務(wù)器,但是Sentinel并不需要讀取RDB/AOF文件來還原數(shù)據(jù)狀態(tài)。
Sentinel用于較少的Redis命令,大部分命令在Sentinel客戶端都不支持,并且Sentinel擁有一些特殊的功能,這些需要Sentinel在啟動時將Redis服務(wù)器使用的代碼替換為Sentinel的專用代碼。在此期間Sentinel會載入與普通Redis服務(wù)器不同的命令表。 Sentinel不支持SET、DBSIZE等命令;保留支持PING、PSUBSCRIBE、SUBSCRIBE、UNSUBSCRIBE、INFO等指令;這些指令在Sentinel工作中提供了保障。
裝載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;
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):
當(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的初始信息)
Sentinel在創(chuàng)建命令連接完成之后,每隔10秒鐘向Master發(fā)送一次INFO指令,通過Master的回復(fù)信息可以獲得兩方面的知識:
Master本身的信息
Master下的Slave信息
根據(jù)主服務(wù)獲取從服務(wù)器信息,Sentinel可以創(chuàng)建到Slave的網(wǎng)絡(luò)連接,Sentinel和Slave之間也會創(chuàng)建命令連接和訂閱連接。
當(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)信息如下所示:
此時是不是還有疑問,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)建命令連接:
此時實(shí)例結(jié)構(gòu)信息如下所示:
Sentinel最主要的工作就是監(jiān)視Redis服務(wù)器,當(dāng)Master實(shí)例超出預(yù)設(shè)的時限后切換新的Master實(shí)例。這其中有很多細(xì)節(jié)工作,大致分為檢測Master是否主觀下線、檢測Master是否客觀下線、選舉領(lǐng)頭Sentinel、故障轉(zhuǎn)移四個步驟。
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
當(dāng)前Sentinel認(rèn)為其下線只能處于主觀下線狀態(tài),要想判斷當(dāng)前Master是否客觀下線,還需要詢問其他Sentinel,并且所有認(rèn)為Master主觀下線或者客觀下線的總和需要達(dá)到quorum配置的值,當(dāng)前Sentinel才會將Master標(biāo)志為客觀下線。
當(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
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)生為止)。
故障轉(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)流程即可!
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)開展:
資源清單:
節(jié)點(diǎn) | IP | 槽(slot)范圍 |
---|---|---|
Master[0] | 192.168.211.107:6319 | Slots 0 - 5460 |
Master[1] | 192.168.211.107:6329 | Slots 5461 - 10922 |
Master[2] | 192.168.211.107:6339 | Slots 10923 - 16383 |
Slave[0] | 192.168.211.107:6369 | |
Slave[1] | 192.168.211.107:6349 | |
Slave[2] | 192.168.211.107:6359 |
Redis集群.png
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ù)了,嘿嘿嘿;
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如下所示:
假設(shè)集群如上面我給出的資源清單,此時代表Master[0]的clusterNode的slots如下所示:
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;
每個節(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集群中使用兩個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)了解,后面再說吧,這里說的話順序不太對。
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ù)組。
這個其實(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ù)組
在了解這個問題之前先要知道一個點(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é)完成。
這個問題其實(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)也更新了新的槽分配信息。
優(yōu)秀的你總會想到這種并發(fā)情況,牛皮呀!大佬們!
這個問題官方也考慮了,還記得我們在聊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)需要注意的地方**(因此我放個表情包在這里,方便讀者注意)。**
前面說了,當(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命令)。
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è)資訊頻道!
免責(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)容。