一文詳解redis高可用Sentinel?
寫在前面
redis 在我們?nèi)粘5臉I(yè)務(wù)開發(fā)中是十分常見的,而redis的可用性就必須要有很高的要求,那么 redis集群的高可用由有一個(gè)或者多個(gè) Sentinel(哨兵) 實(shí)例組成的 哨兵系統(tǒng)來保證的。
哨兵
由一個(gè)或者多個(gè) Sentinel 實(shí)例組成的 Sentinel 系統(tǒng)可以監(jiān)控任意多個(gè)主服務(wù)器,以及這些主服務(wù)器屬下的所有從服務(wù)器,并在被監(jiān)視的主服務(wù)器進(jìn)入下線狀態(tài)時(shí),自動(dòng)將下線主服務(wù)器屬下的某個(gè)從服務(wù)器升級(jí)為新主服務(wù)器,然后有新的主服務(wù)器代替已下線的主服務(wù)器繼續(xù)處理命令請(qǐng)求。

簡(jiǎn)介
Sentinel 本質(zhì)上只是一個(gè)運(yùn)行在特殊模式下的Redis服務(wù)器,但是 Sentinel 和 Redis的初始化和工作內(nèi)容是不同的。Sentinel 不需要使用數(shù)據(jù)庫,所以初始化的時(shí)候是不需要載入RDB文件或者AOF文件的。而Sentinel的工作內(nèi)容如下:
| 功能 | 使用情況 | 
|---|---|
| 數(shù)據(jù)庫鍵值對(duì)命令 SET、DEL、FLUSHDB | 不使用 | 
| 事務(wù)命令 MULTI,WATCH | 不使用 | 
| 腳本命令 EVAL | 不使用 | 
| RDB/AOF持久化命令 SAVE、BGSAVE、BGREWRITEAOF | 不使用 | 
| 復(fù)制命令,SLAVEOF | Sentinel 內(nèi)部使用,但是客戶端不用 | 
| 發(fā)布與訂閱命令,比如 PUBLISH 和 SUBSCRIBE | SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUBSUBSCRIBE這四個(gè)命令在Sentinel內(nèi)部和客戶端都可以使用,但PUBLISH命令只能在Sentinel內(nèi)部使用 | 
| 文件事件處理器(負(fù)責(zé)發(fā)送命令和請(qǐng)求、處理命令回復(fù)) | Sentinel 內(nèi)部使用,但關(guān)聯(lián)的文件事件處理器和普通Redis處理器不同 | 
| 事件處理器(負(fù)責(zé)執(zhí)行serverCron 函數(shù)) | Sentinel 內(nèi)部使用,時(shí)間事件的處理器仍然是ServerCron函數(shù),ServerCron函數(shù)會(huì)調(diào)用sentinel.c/sentineTimer函數(shù),后者包含了Sentinel要執(zhí)行的所有操作 | 
在為什么啟動(dòng)Sentinel的時(shí)候,會(huì)有這些限制呢?Sentinel 不都是 Redis 嗎?
因?yàn)樵赟entinel初始化的時(shí)候,加載的是
src/sentinel.c文件的函數(shù),而Redis加載的是src/redis.c文件的函數(shù),而這兩個(gè)文件的初始化函數(shù),限定了只能使用哪些命令
數(shù)據(jù)結(jié)構(gòu)
Sentinel 的結(jié)構(gòu)體如下
typedef struct sentinelRedisInstance {
    int flags;      // 標(biāo)識(shí),記錄實(shí)例類型
    char *name;     // 該實(shí)例名字
    char *runid;    // 實(shí)例的運(yùn)行id
    uint64_t config_epoch;  // 配置紀(jì)元,用于實(shí)現(xiàn)故障轉(zhuǎn)移
    sentinelAddr *addr; 	// 實(shí)例地址
    mstime_t last_pub_time;   // 上次我們通過 Pub/Sub 發(fā)送了 hello 的時(shí)間。 
    mstime_t last_hello_time; // 僅在設(shè)置 SRI_SENTINEL 時(shí)使用。上次我們發(fā)送hello的響應(yīng)時(shí)間
    mstime_t last_master_down_reply_time; // SENTINEL is-master-down command.命令的最新響應(yīng)時(shí)間
	// ... 
    mstime_t down_after_period; // 實(shí)例無響應(yīng)多少毫秒之后才會(huì)被判斷為主觀下線
	// ...
    // Master 配置
    unsigned int quorum;// 判斷這個(gè)實(shí)例為客觀下線鎖需要支持的投票數(shù)量
	// ... 
    // Slave 配置
	int slave_priority; /* Slave 優(yōu)先級(jí) */
    struct sentinelRedisInstance *master; /* Master instance if it's slave. */
    char *slave_master_host;    /* Master host as reported by INFO */
    int slave_master_port;      /* Master port as reported by INFO */
    int slave_master_link_status; /* Master link status as reported by INFO */
    unsigned long long slave_repl_offset; /* Slave 復(fù)制的偏移量. */
    // ...
} sentinelRedisInstance;
創(chuàng)建鏈接
初始化Sentinel的最后一步是創(chuàng)建連向被監(jiān)控主服務(wù)器的網(wǎng)絡(luò)連接,Sentinel 將成為主服務(wù)器的客戶端,可以向主服務(wù)器發(fā)送命令,并且從命令回復(fù)中獲取相關(guān)的信息。
對(duì)于每個(gè)被Sentinel 監(jiān)視的主服務(wù)器來說,Sentinel會(huì)創(chuàng)建兩個(gè)連接主服務(wù)器的異步網(wǎng)絡(luò)連接:
- 命令連接,這個(gè)連接專門用于向主服務(wù)器發(fā)送命令,并接受命令回復(fù)。
 - 訂閱連接,這個(gè)連接專門用于訂閱主服務(wù)器的 
__sentinel__:hello頻道。 
為什么會(huì)有兩個(gè)連接?
在Redis目前的發(fā)布與訂閱功能中,被i發(fā)送的信息都不會(huì)保存在Redis服務(wù)器里面,如果在信息發(fā)送時(shí),想要接受信息的客戶端不在線或者斷線,那么這個(gè)客戶端就會(huì)丟失這條信息。 因此為了不丟失__sentinel__:hello 頻道的任何信息,Sentinel必須專門用一個(gè)連接來接受該頻道的信息。除了訂閱頻道之外,Sentinel還必須向主服務(wù)器發(fā)送命令,以此來與主服務(wù)器進(jìn)行通信,所以Sentinel還必須向主服務(wù)器創(chuàng)建命令連接。

獲取信息 INFO
Sentinel 默認(rèn)會(huì)以每十秒一次的頻率,通過命令連接向被監(jiān)視的主服務(wù)器發(fā)送 INFO命令 ,并通過分析 INFO 命令的回復(fù) 來獲取主服務(wù)器的當(dāng)前信息。
一般INFO命令的回復(fù)有以下信息:
- 從服務(wù)器的運(yùn)行ID、角色role、優(yōu)先級(jí)slave_priority、復(fù)制偏移量
 - 主服務(wù)器的IP地址 master_host 以及主服務(wù)器的端口號(hào) master_port
 - 主從服務(wù)器的連接狀態(tài)master_link_status
 
獲取到這些信息之后,就會(huì)更新存儲(chǔ)到Sentinel的結(jié)構(gòu)體中。

但是當(dāng)主服務(wù)器處于下線狀態(tài),或者Sentinel正在對(duì)主服務(wù)器和從服務(wù)器進(jìn)行故障轉(zhuǎn)移操作時(shí),Sentinel 向從服務(wù)器發(fā)送INFO命令的頻率將會(huì)變成每秒一次。
發(fā)送命令
對(duì)于監(jiān)視同一個(gè)主服務(wù)器和從服務(wù)器的多個(gè)Sentinel來說,他們會(huì)以每?jī)擅胍淮蔚念l率,通過被監(jiān)視服務(wù)器的 __sentinel:hello__ 頻道發(fā)送消息來響應(yīng)其他sentinel宣告自己的存在。
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
這條命令向服務(wù)器的__sentinel__:hello頻道發(fā)送一條信息,這些信息的組成如下:
- s_ 開頭的是sentinel本身的信息。
 - m_開頭的是主服務(wù)器的信息。如果此sentinel監(jiān)視的是主服務(wù)器,那么這個(gè)參數(shù)就是主服務(wù)器的參數(shù),如果監(jiān)視的是從服務(wù)器,那么就是這個(gè)從服務(wù)器正在所復(fù)制的主服務(wù)器。
 
接收命令
當(dāng)sentinel與一個(gè)主服務(wù)器或者從服務(wù)器建立起訂閱連接之后,sentinel就會(huì)就會(huì)通過訂閱連接,向服務(wù)器發(fā)送以下命令:
SUBSCRIBE __sentinel__:hello
也就是說對(duì)于每一個(gè)sentinel連接的服務(wù)器,sentinel既通過命令連接到服務(wù)器的__sentinel__:hello 頻道發(fā)送信息,又通過訂閱連接服務(wù)器的__sentinel__:hello頻道接受消息。

那么對(duì)于監(jiān)視同一個(gè)服務(wù)器的多個(gè)sentinel來說,一個(gè)sentinel發(fā)送的信息就會(huì)被其他sentinel接受到,因?yàn)槭潜O(jiān)聽訂閱了同一個(gè)服務(wù)器的__sentinel__:hello頻道,所sentinel就會(huì)感知到其他sentinel的存在。并sentinel將會(huì)更新其他的sentinel信息到自己的sentinel字典中。

sentinel 之間的通信
從上面我們知道每個(gè)Sentinel也會(huì)從__sentinel:hello__ 頻道中接收其他Sentinel發(fā)送來的信息,并根據(jù)這些信息為其他Sentinel創(chuàng)建實(shí)例結(jié)構(gòu)和命令連接。

但是Sentinel 只會(huì)與主服務(wù)器和從服務(wù)器創(chuàng)建命令連接和訂閱連接,Sentinel 和 Sentinel 之間則只創(chuàng)建命令連接。
為什么sentinel與sentinel之間不需要?jiǎng)?chuàng)建訂閱連接呢?
首先我們要確定訂閱連接是用來干嘛的,訂閱連接是用來發(fā)現(xiàn)其他節(jié)點(diǎn)的,而sentinel已經(jīng)通過主服務(wù)器或者從服務(wù)器的頻道信息來發(fā)現(xiàn)未知的sentinel,也就是說sentinel訂閱了主/從服務(wù)器已經(jīng)知道了其他的sentinel,就不需要再進(jìn)行訂閱連接其他的sentinel了,而相互已知的sentinel只需要使用命令連接來進(jìn)行通信就夠了。
主/客觀下線
Sentinel 會(huì)以每秒一次的頻率向?qū)嵗?strong>包括主服務(wù)器,從服務(wù)器,其他Sentinel發(fā)送 PING 命令,并根據(jù)實(shí)例對(duì)PING命令的回復(fù)判斷實(shí)例是否在線,當(dāng)一個(gè)實(shí)例在指定的時(shí)長(zhǎng)中連續(xù)向Sentinel發(fā)送無效回復(fù)時(shí),Sentinel就會(huì)判斷為主觀下線。

Sentinel1 將向 Sentinel2、server、slave1、slave2發(fā)送ping命令。sentinel2也會(huì)進(jìn)行同樣的操作。那么一般會(huì)得到以下兩種情況的回復(fù)
- 有效回復(fù):返回 PONG、LOADING、MASTERDOWN 三個(gè)中的一個(gè)
 - 無效回復(fù):非有效回復(fù)的內(nèi)容,或者是指定時(shí)間內(nèi)沒有返回任何的回復(fù),而這個(gè)指定時(shí)間的字段為
down-after-milliseconds。 
當(dāng)Sentinel講一個(gè)主服務(wù)器判斷為主觀下線,他會(huì)向同樣監(jiān)視這個(gè)主服務(wù)器的其他 Sentinel 進(jìn)行詢問,如果有足夠多的結(jié)點(diǎn)判定這個(gè)主服務(wù)器為主觀下線,那么就狀態(tài)改成客觀下線,某個(gè)節(jié)點(diǎn)的狀態(tài)改成客觀下線之后,監(jiān)視這個(gè)節(jié)點(diǎn)的各個(gè)sentinel就會(huì)協(xié)商選取一個(gè)leader sentinel節(jié)點(diǎn),并且由領(lǐng)頭的 leader sentinel 節(jié)點(diǎn)發(fā)起一次針對(duì)主服務(wù)器的故障轉(zhuǎn)移。
這個(gè)選舉的過程在這里就不過多介紹了。有點(diǎn)類似raft。后面有空再說明。
故障轉(zhuǎn)移
在選舉出leader sentinel節(jié)點(diǎn)之后的故障轉(zhuǎn)移會(huì)做以下幾件事情:
- 在已下線主服務(wù)器屬下的所有從服務(wù)器里面,挑選出一個(gè)從服務(wù)器,并將其轉(zhuǎn)化成
主服務(wù)器。 - 讓已下線的主服務(wù)器屬下的所有從服務(wù)器改成復(fù)制新的主服務(wù)器。
 - 將已下線主服務(wù)器設(shè)置為
新的從服務(wù)器 
新的主服務(wù)器是如何挑選出來的呢?首先leader sentinel節(jié)點(diǎn)會(huì)將已下線主服務(wù)器的所有從服務(wù)器保存到一個(gè)列表,根據(jù)一些規(guī)則進(jìn)行過濾:
- 刪除已下線或者狀態(tài)不正常的從服務(wù)器,保證列表中剩余的從服務(wù)器是
正常的。 - 刪除所有最近5秒內(nèi)沒有回復(fù)leader sentinel節(jié)點(diǎn)的INFO命令的從服務(wù)器,保證列表中的從服務(wù)器都是
最新通信成功的。 - 刪除與已下線的主服務(wù)器連接斷開超過 down-after-milliseconds * 10 毫秒的從服務(wù)器,保證剩余的服務(wù)器保存的
數(shù)據(jù)都是比較新的 
down-after-milliseconds:實(shí)例失去聯(lián)系的時(shí)間,而刪除斷開這個(gè)時(shí)間的10倍,是為了能保證剩余的從服務(wù)器沒有過早的與主服務(wù)器斷開連接。
- 會(huì)根據(jù)從服務(wù)器的優(yōu)先級(jí)進(jìn)行排序,選擇最高優(yōu)先級(jí)的從服務(wù)器,如果相同優(yōu)先級(jí),則選擇偏移量最大服務(wù)器。因?yàn)槠屏看笠馕吨鴶?shù)據(jù)最新。
 
易主
選擇完主服務(wù)器之后,就開始改變從服務(wù)器的復(fù)制對(duì)象了。這一動(dòng)作可以通過向服務(wù)器發(fā)送SLAVEOF的命令來實(shí)現(xiàn)。

本文我們?cè)敿?xì)介紹了redis集群中sentinel的數(shù)據(jù)結(jié)構(gòu),sentinel與主從服務(wù)器的連接,信息傳遞,以及主從服務(wù)器發(fā)生故障時(shí)的處理方式。
那么問題來了?如果sentinel 集群中某一個(gè)sentinel節(jié)點(diǎn)掛了會(huì)發(fā)送什么事情呢?
到此這篇關(guān)于redis 高可用 Sentinel 詳解的文章就介紹到這了,更多相關(guān)redis 高可用 Sentinel內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
 從零搭建SpringBoot2.X整合Redis框架的詳細(xì)教程
這篇文章主要介紹了從零搭建SpringBoot2.X整合Redis框架的詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
 Linux中設(shè)置Redis開機(jī)啟動(dòng)的方法
這篇文章主要給大家介紹了關(guān)于Linux中設(shè)置Redis開機(jī)啟動(dòng)的方法,主要包括在CentOS7.0系統(tǒng)和Debian 8.0系統(tǒng)下實(shí)現(xiàn)方法,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-04-04
 SpringBoot整合Redis實(shí)現(xiàn)序列化存儲(chǔ)Java對(duì)象的操作方法
這篇文章主要介紹了SpringBoot整合Redis實(shí)現(xiàn)序列化存儲(chǔ)Java對(duì)象,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03

