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