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