Sentinel哨兵
這是《Redis設(shè)計(jì)與實(shí)現(xiàn)》系列的文章,系列導(dǎo)航:Redis設(shè)計(jì)與實(shí)現(xiàn)筆記
哨兵:監(jiān)視、通知、自動(dòng)故障恢復(fù)
啟動(dòng)與初始化
Sentinel 的本質(zhì)只是一個(gè)運(yùn)行在特殊模式下的 Redis 服務(wù)器,所以啟動(dòng) Sentinel 的步驟如下:
-
初始化一個(gè)普通的 Redis 服務(wù)器,不過也有一些不同:
-
將一部分 Redis 服務(wù)器使用的代碼替換成 Sentinel 專用代碼
舉兩個(gè)例子:
-
服務(wù)器端口由
redis.h/REDIS_SERVERPORT
修改為sentinel.c/REDIS_SENTINELPORT
-
服務(wù)器的命令表替換為
sentinel.c/sentinelcmds
// 服務(wù)器在 sentinel 模式下可執(zhí)行的命令 struct redisCommand sentinelcmds[] = { {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0}, {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0}, {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0}, {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0}, {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0}, {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}, {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0} };
-
-
初始化 Sentinel 狀態(tài)
可以看一下這個(gè)狀態(tài)的定義:
/* Main state. */ /* Sentinel 的狀態(tài)結(jié)構(gòu) */ struct sentinelState { // 當(dāng)前紀(jì)元 uint64_t current_epoch; /* Current epoch. */ // 保存了所有被這個(gè) sentinel 監(jiān)視的主服務(wù)器 // 字典的鍵是主服務(wù)器的名字 // 字典的值則是一個(gè)指向 sentinelRedisInstance 結(jié)構(gòu)的指針 dict *masters; // 是否進(jìn)入了 TILT 模式? int tilt; /* Are we in TILT mode? */ // 目前正在執(zhí)行的腳本的數(shù)量 int running_scripts; /* Number of scripts in execution right now. */ // 進(jìn)入 TILT 模式的時(shí)間 mstime_t tilt_start_time; /* When TITL started. */ // 最后一次執(zhí)行時(shí)間處理器的時(shí)間 mstime_t previous_time; /* Last time we ran the time handler. */ // 一個(gè) FIFO 隊(duì)列,包含了所有需要執(zhí)行的用戶腳本 list *scripts_queue; /* Queue of user scripts to execute. */ } sentinel;
-
初始化 Sentinel 狀態(tài)的 masters 屬性
dict *masters;
是一個(gè)字典結(jié)構(gòu),鍵是被監(jiān)視主服務(wù)器的名稱,值是主服務(wù)器對應(yīng)的sentinel.c/sentinelReidsInstance
結(jié)構(gòu)這個(gè)初始化是根據(jù)被載入的 Sentinel 配置文件來進(jìn)行的
-
創(chuàng)建網(wǎng)絡(luò)連接
Sentinel 將成為主服務(wù)器的客戶端,它可以向主服務(wù)器發(fā)送命令,并從命令回復(fù)中獲取相關(guān)的信息。會(huì)創(chuàng)建兩個(gè)連向主服務(wù)器的異步網(wǎng)絡(luò)連接:
- 一個(gè)是命令連接,專門用于向主服務(wù)器發(fā)送命令,并接收命令回復(fù)
- 一個(gè)是訂閱連接,專用用于訂閱主服務(wù)器的
__sentinel__:hello
頻道(訂閱的好處是可以防止消息丟失)
與服務(wù)器進(jìn)行通信
獲取主服務(wù)器信息
如上圖所示:
- Sentinel 默認(rèn)會(huì)以每10秒一次的頻率發(fā)送 INFO 命令,獲取主節(jié)點(diǎn)的信息
- 主節(jié)點(diǎn)會(huì)返回自身和其從節(jié)點(diǎn)的信息
- Sentinel 接收到信息后,更新自己的 masters 字典,如果有新節(jié)點(diǎn),則創(chuàng)建之
獲取從服務(wù)器信息
當(dāng) Sentinel 發(fā)現(xiàn)主服務(wù)器有新的從節(jié)點(diǎn)時(shí),會(huì)創(chuàng)建到從節(jié)點(diǎn)的命令連接和訂閱鏈接:
同樣的,以10秒一次的頻率發(fā)送 INFO 命令并獲取返回信息:
并更新自己保存的信息。
發(fā)送頻道信息
默認(rèn)情況下,Sentinel會(huì)以每兩秒一次的頻率,通過命令向所有被監(jiān)視的主服務(wù)器和從服務(wù)器發(fā)送命令:
PUBLISH __sentinel__:hello "xxx"
這條命令向服務(wù)器的 __sentinel__
頻道發(fā)送了一條消息,在上面我用"xxx"表示出來了,其具體組成有:
即兩部分:
- 自己的信息
- 主服務(wù)器的信息
接收頻道消息
前面提到了,Sentinel 會(huì)向服務(wù)器的頻道發(fā)送信息:
PUBLISH __sentinel__:hello "xxx"
另一方面,Sentinel 還會(huì)訂閱所有被監(jiān)視服務(wù)器的頻道:
SUBSCRIBE __sentinel__:hello
對于監(jiān)視同一個(gè)服務(wù)器的多個(gè) Sentinel 來說,這些消息會(huì)被用于更新其他 Sentinel 對發(fā)送信息的 Sentinel 的認(rèn)知,也會(huì)被用于更新其他 Sentinel 對被監(jiān)視服務(wù)器的認(rèn)知。
而更新的具體數(shù)據(jù)是:sentinelState 結(jié)構(gòu)體的 dict *masters;
變量(上文提到過)指向的 sentinelRedisInstance
的 sentinels
字典變量(這個(gè)變量保存了所有監(jiān)視這個(gè)服務(wù)器的 Sentinel)
- 鍵位Sentinel的IP和端口
- 值指向sentinel實(shí)例
而具體的更新流程是:
這樣做的一個(gè)好處是,可以自動(dòng)發(fā)現(xiàn)其他 Sentinel,并形成相互連接的網(wǎng)絡(luò),而無需手動(dòng)配置。
Sentinel 之間只會(huì)創(chuàng)建命令鏈接,而不會(huì)創(chuàng)建訂閱鏈接。
因?yàn)橹院头?wù)器需要?jiǎng)?chuàng)建訂閱鏈接就是用來發(fā)現(xiàn)未知的新的 Sentinel 的。
服務(wù)器意外狀態(tài)
檢測主觀下線狀態(tài)
Sentinel 會(huì)以每秒一次的頻率向所有與他建立了命令簡介的實(shí)例(包括主、從、Sentinel服務(wù)器)發(fā)送 PING 命令,并通過返回信息判斷實(shí)例的狀態(tài)。
實(shí)例對 PING 的回復(fù)有兩種:
- 有效回復(fù):
+PING
、-LOADING
、-MASTERDOWN
- 無效回復(fù):其他內(nèi)容或超時(shí)
如果一個(gè)實(shí)例在 down-after-milliseconds
配置的時(shí)間內(nèi)沒有返回有效回復(fù),就會(huì)被標(biāo)記為主觀下線狀態(tài)
檢測客觀下線狀態(tài)
Sentinel 也要問問別的監(jiān)控目標(biāo)的 Sentinel 的意見,才好決定是否是真的下線了。
is-master-down-by-addr
有幾個(gè)參數(shù),包含了:
- ip、port:被審判的主機(jī)的ip和端口號(hào)
- current_epoch:當(dāng)前的配置紀(jì)元,用以選舉領(lǐng)頭 Sentinel 進(jìn)行故障轉(zhuǎn)移
- runid:
*
表示判斷客觀下線- 如果是 Sentinel 的運(yùn)行 ID 則用來選舉領(lǐng)頭
multi bulk
是 Sentinel 的返回值(為什么叫這個(gè)名字?文檔是這么叫的),包含了三個(gè)值:
- down_state:是否下線
- leader_runid:
*
表示僅僅用以檢測服務(wù)器的下線狀態(tài)- 如果是領(lǐng)頭 Sentinel 的 ID 則說明用于選舉領(lǐng)頭 Sentinel
- leader_epoch:
- 如果leader_runid為
*
,則為0 - 否則為配置紀(jì)元
- 如果leader_runid為
你應(yīng)該看出來了,上面的兩條命令有兩種作用:
- 判斷是否下線
- 選舉領(lǐng)頭 Sentinel
選舉領(lǐng)頭 Sentinel
當(dāng)一個(gè)主服務(wù)器被判斷為客觀下線后,監(jiān)視這個(gè)服務(wù)器的各個(gè) Sentinel 會(huì)進(jìn)行協(xié)商,選舉一個(gè)領(lǐng)頭的 Sentinel 并進(jìn)行故障轉(zhuǎn)移。
我的理解:
這里只有中間的 Sentinel 確定了客觀下線這一事實(shí),其他的 Sentinel 未必認(rèn)同,但是即便如此,只要有一個(gè) Sentinel 認(rèn)定了客觀下線的情況,其他 Sentinel 也會(huì)配合進(jìn)行選舉、故障轉(zhuǎn)移。
選舉的策略是:
- 所有人都有機(jī)會(huì)當(dāng)選
- 發(fā)現(xiàn)主觀下線的會(huì)向其他選手拉選票
- 所有人都是給第一個(gè)要求投票的人
- 超過一半選票的人當(dāng)選
如果在給定時(shí)限中沒有選出leader,則在一段時(shí)間后再次進(jìn)行選舉,直到選出leader。
這么一種做法有沒有可能在很長的一段時(shí)間內(nèi)都發(fā)生選舉失敗的情況呢?
這個(gè)可能要之后學(xué)習(xí)一下Raft算法的領(lǐng)頭選舉算法。
故障轉(zhuǎn)移
領(lǐng)頭 leader 將對已下線的主服務(wù)進(jìn)行故障轉(zhuǎn)移操作:
- 選一個(gè)新的主服務(wù)器
- 讓前任主服務(wù)器的所有從服務(wù)器跟著現(xiàn)任服務(wù)器
- 將前任設(shè)置為現(xiàn)任的從服務(wù)器
如何選新的服務(wù)器:
- 篩選排除:
- 下線的、斷線狀態(tài)的
- 最近5秒內(nèi)都沒有回復(fù)過leader的INFO命令的服務(wù)器
- 與前任主服務(wù)器斷開超過
down-after-milliseconds * 10
的服務(wù)器- 優(yōu)先選擇:
- 優(yōu)先級較高
- 復(fù)制偏移量較大
- ID最小
本文摘自 :https://www.cnblogs.com/