redis 哨兵
哨兵作用
哨兵(sentinel) 是一個分散式系統,是程式高可用性的一個保障。用於監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,當出現故障時通過投票機制選擇新的master並將所有slave連接到新的master。
監控
不斷地檢查master和slave是否正常運行 master存活檢測、master與slave運行情況檢測。
通知
當被監控地伺服器出現問題時,向其他(哨兵間,客戶端)發送通知。
自動故障轉移
斷開master與slave連接,選取一個slave作為master,將其他slave連接到新的master,並告知客戶端新的伺服器地址。
注意
哨兵也是一台redis伺服器,只是不提供數據服務,通常哨兵配置數量為單數
啟動哨兵
配置文件
哨兵默認的配置文件 sentinel.conf
一般的以 sentinel_port.conf 命名 哨兵的配置文件
配置資訊
port 26379 (埠號)
dir /tmp (哨兵運行資訊存儲)
monitor mymaster 127.0.0.1 6379 2
# mymaster (master 名字 隨意)
# 127.0.0.1 6379 (IP + 埠號)
# 2 (哨兵個數 //2 + 1 當有 2 個哨兵認為 master 掛了 就掛了)
down-after-milliseconds mymaster 30000 (單位 毫秒 )
parallel-syncs mymaster 1 ( 新的master 一次有多少個 slave 同步,設置的越小,完成數據同步的時間越長,響應的伺服器壓力越小。)
failover-timeout mymaster 180000( 3 分鐘 如果沒有同步完成 就判定為同步超時)
啟動
配置主從結構,以 1master 2 slave為例。
1 先啟動 master 和 slave
主從配置 參看 主從篇部落格主從
redis-server config_6379.conf
redis-server config_6380.conf
redis-server config_6381.conf
2 啟動哨兵
redis-sentinel sentinel_26379.conf
redis-sentinel sentinel_26380.conf
redis-sentinel sentinel_26381.conf
Sentinel 命令
PING:PONG
SENTINEL masters :列出所有被監視的主伺服器,以及這些主伺服器的當前狀態。
SENTINEL slaves :列出給定主伺服器的所有從伺服器,以及這些從伺服器的當前狀態。
SENTINEL get-master-addr-by-name : 返回給定名字的主伺服器的 IP 地址和埠號。 如果這個主伺服器正在執行故障轉移操作, 或者針對這個主伺服器的故障轉移操作已經完成, 那麼這個命令返回新的主伺服器的 IP 地址和埠號。
SENTINEL reset : 重置所有名字和給定模式 pattern 相匹配的主伺服器。 pattern 參數是一個 Glob 風格的模式。 重置操作清楚主伺服器目前的所有狀態, 包括正在執行中的故障轉移, 並移除目前已經發現和關聯的, 主伺服器的所有從伺服器和 Sentinel 。
SENTINEL failover : 當主伺服器失效時, 在不詢問其他 Sentinel 意見的情況下, 強制開始一次自動故障遷移 (不過發起故障轉移的 Sentinel 會向其他 Sentinel 發送一個新的配置,其他 Sentinel 會根據這個配置進行相應的更新)。
初始化Sentinel
初始化伺服器
從下面啟動程式碼可以看出啟動方式由函數 checkForSentinelMode 來決定,是否使用 sentinel 的模式進行一個啟動, 添加的指令也是用的 sentinelcmds 的命令表
int checkForSentinelMode(int argc, char **argv) {
int j;
if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
for (j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
// 檢查伺服器是否以 Sentinel 模式啟動
server.sentinel_mode = checkForSentinelMode(argc,argv);
// 初始化伺服器
initServerConfig(); // 在第二步介紹該函數
// 如果伺服器以 Sentinel 模式啟動,那麼進行 Sentinel 功能相關的初始化
// 並為要監視的主伺服器創建一些相應的數據結構
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
從源碼我們可以看出哨兵的啟動有兩種方式
redis-sentinel sentinel_xxx.conf
redis-server sentinel_xxx.conf --sentinel
無論哪種方式啟動redis,都會執行 initServerConfig ,不同的是 Sentinel 還會 執行initSentinelConfig、initSentinel 兩個初始化函數。接下來看看這兩個函數都幹了什麼~ 。
替換 Sentinel 的專用程式碼
initSentinelConfig() 這個函數會用 Sentinel 配置的屬性覆蓋伺服器默認的屬性。
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;//26379
}
initSentinel() 會進行一個命令表的載入。一個主要的查詢命令 INFO 也不同於普通伺服器,而是使用一個特殊的版本。
// 初始化伺服器 Sentinel 伺服器
void initSentinel(void) {
int j;
// 刪除 普通 Redis 伺服器的命令表(該表用於普通模式)
dictEmpty(server.commands,NULL);
// 添加 sentinel 模式專用的命令。
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
int retval;
struct redisCommand *cmd = sentinelcmds+j;
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
redisAssert(retval == DICT_OK);
}
/* 初始化 Sentinel 的狀態 這是為了故障轉移階段選取 切換執行者 記錄的狀態 */
sentinel.current_epoch = 0;
// 保存 主伺服器 資訊的字典 (這裡記錄了監測的主伺服器的資訊)
sentinel.masters = dictCreate(&instancesDictType,NULL);
// 初始化 TILT 模式的相關選項
sentinel.tilt = 0;
sentinel.tilt_start_time = 0;
sentinel.previous_time = mstime();
// 初始化腳本相關選項
sentinel.running_scripts = 0;
sentinel.scripts_queue = listCreate();
}
// sentinel 的指令集合
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 狀態
在完成命令表載入之後,緊接著會進行 sentinelState 和 sentinelRedisInstance 結構的一個初始化。
Sentinel 狀態中的 masters 字典記錄了所有被監視的主伺服器資訊,鍵為伺服器名字,值為被監視主伺服器對應的sentinel.c/sentinelRedisInstance結構。每個sentinelRedisInstance實例結構代表監視一個Redis伺服器實例,這個實例可以是主伺服器,也可以是從伺服器,或者另外一個sentinel伺服器。
對於sentinelState的初始化將引發對masters字典的初始化,而masters字典的初始化是根據被該入的sentinel配置文件(sentinel_26379.conf)來進行的。主要為被監控 master 的ip 和 port。
注意 這些都是有 sentinel 來維護和使用的。
sentinelState
struct sentinelState {
// 當前紀元 用做故障轉移
uint64_t current_epoch; /* Current epoch. */
// 保存了所有被這個 sentinel 監視的主伺服器
// 字典的鍵是主伺服器的名字
// 字典的值則是一個指向 sentinelRedisInstance 結構的指針,可以是主伺服器,從伺服器或者其他sentinel節點
dict *masters; /* Dictionary of master sentinelRedisInstances.
Key is the instance name, value is the
sentinelRedisInstance structure pointer. */
// 是否進入了 TILT 模式?
int tilt; /* Are we in TILT mode? */
// 目前正在執行的腳本的數量
int running_scripts; /* Number of scripts in execution right now. */
// 進入 TILT 模式的時間
mstime_t tilt_start_time; /* When TITL started. */
// 最後一次執行時間處理器的時間
mstime_t previous_time; /* Last time we ran the time handler. */
// 一個 FIFO 隊列,包含了所有需要執行的用戶腳本
list *scripts_queue; /* Queue of user scripts to execute. */
} sentinel;
sentinelRedisInstance
name
實例的名字
主伺服器的名字由用戶在配置文件中設置
從伺服器以及 Sentinel 的名字由 Sentinel 自動設置
格式為 ip:port ,例如 "127.0.0.1:26379"
runid
實例的運行 ID
sentinelAddr
實例的地址
主伺服器實例特有的屬性
sentinels
其他同樣監控這個主伺服器的所有 sentinel
slaves
如果這個實例代表的是一個主伺服器
那麼這個字典保存著主伺服器屬下的從伺服器
字典的鍵是從伺服器的名字,字典的值是從伺服器對應的 sentinelRedisInstance 結構
quorum
判斷這個實例為客觀下線(objectively down)所需的支援投票數量
parallel_syncs
SENTINEL parallel-syncs
在執行故障轉移操作時,可以同時對新的主伺服器進行同步的從伺服器數量
auth_pass
連接主伺服器和從伺服器所需的密碼
從伺服器實例特有的屬性
master_link_down_time
主從伺服器連接斷開的時間
slave_priority
從伺服器優先順序
slave_reconf_sent_time
執行故障轉移操作時,從伺服器發送 SLAVEOF
master
主伺服器的實例(在本實例為從伺服器時使用)
slave_master_host
INFO 命令的回復中記錄的主伺服器 IP
slave_master_port
INFO 命令的回復中記錄的主伺服器埠號
slave_master_link_status
INFO 命令的回復中記錄的主從伺服器連接狀態
slave_repl_offset
從伺服器的複製偏移量
結構中的 sentinelAddr 保存著對象的 地址和埠。
/* Address object, used to describe an ip:port pair. */
/* 地址對象,用於保存 IP 地址和埠 */
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
建立連接
sentinel 會先去連接 sentinel 中 masters 中的每一個 master,並在每一個 master 和 sentinel之間創建兩個非同步連接 一個 命令連接 一個 訂閱鏈接。此時 sentinel將成為 master 的客戶端它可以向主伺服器發送命令,並從命令回復中獲取相關資訊。
命令連接
專門用於向主伺服器發送命令,並接收命令回復。比如sentinel向主伺服器發送INFO命令。
訂閱連接
專門用於訂閱主伺服器的 _sentinel_:hello頻道。 比如 sentinel向主,從,其它sentinel發送sentinel本身和主庫資訊。
redis在發布與訂閱功能中,被發送的資訊都不會保存在redis伺服器中,若消息到來時,需要接收的客戶端不在線或者斷線,那麼這個客戶端就會丟失這條資訊。為了不丟失_sentinel_:hello頻道的任何資訊,sentinel必須專門的用一個訂閱連接來接收該頻道的資訊。
獲取主伺服器資訊
Sentinel 默認會以每10秒一次的頻率向主伺服器發送INFO命令,通過分析命令回復來獲取主伺服器的當前資訊。Sentinel可以獲取以下兩方面的資訊:
1主伺服器本身的資訊,包括伺服器run_id,role的伺服器角色。
2 主伺服器對應的所有從伺服器的資訊(從伺服器IP和埠)。
獲取從伺服器資訊
當Sentinel發現有新的從伺服器出現時,Sentinel除了會為這個新的從伺服器創建相應的實例結構(sentinelRedisInstance)之外,還會創建到從伺服器的命令連接和訂閱連接。
Sentinel依然會像對待主伺服器那樣,每10s 發送一個INFO命令來獲取從伺服器的當前資訊。
run_id、role、ip、port 、master_link_status(主從伺服器的連接狀態)、slave_priority(從伺服器的優先順序)等資訊。
向主從伺服器發送資訊
在默認情況下, Sentinel會以每2秒一次的頻率,通過命令連接向,所有被監視的主伺服器和從伺服器發送以下格式的命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
這條命令向伺服器的_sentinel_:hello頻道發送了一條資訊,資訊的內容由多個參數組成:
(1) s_開頭的參數記錄的是sentinel本身的資訊。
(2) m_開頭的參數記錄的則是主伺服器的資訊,如果sentinel正在監視的是主伺服器,那麼這些參數就是主伺服器的資訊,如果sentinel正在監視的是從伺服器,那麼這些參數記錄就是從伺服器正在複製的主伺服器的資訊。
| 參數 | 描述 |
|---|---|
| S_ip | Sentinel的ip地址 |
| S_port | Sentinel的埠號 |
| S_runid | Sentinel的運行ID |
| S_epoch | Sentinel 的當前配置紀元 |
| m_name | 主伺服器的名字 |
| M_ip | 主伺服器的IP地址 |
| M_port | 主伺服器的埠號 |
| M_epoch | 主伺服器的當前配置紀元 |
例如
"127.0.0.1,26379,e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc,0,mymaster,127.0.0.1,6379,0"
# --------------------------------解釋------------------------------------------
127.0.0.1 # sentinel ip 地址
26379 # sentinel 埠號
e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc # sentinel的運行 id
0 # sentinel 當前配置紀元
mymaster # sentinel 監控的 master name
127.0.0.1 # master ip 地址
6379 # master 埠號
0 # master 當前配置紀元
接收來自主從伺服器的頻道資訊
當Sentinel與一個主伺服器或者從伺服器建立起訂閱連接之後,Sentinel就會通過訂閱連接向伺服器發送 subscribe_sentinel_:hello 。
對於每個與 Sentinel 連接的伺服器,Sentinel既通過命令連向伺服器的_sentinel_:hello頻道發送資訊,又通過訂閱連接從伺服器的_sentinel_:hello頻道接收資訊。
因此當有新的Sentinel 連接進來時, 向訂閱連接中發送的 subscribe_sentinel_:hello 被已有的Sentinel 接收(同時自己也會接受到來自自己的這條消息)。
// 發送 PUBLISH 命令的間隔
#define SENTINEL_PUBLISH_PERIOD 2000
if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
/* PUBLISH hello messages to all the three kinds of instances. */
sentinelSendHello(ri);
}
/* 接收來自主伺服器和從伺服器的頻道資訊
當 sentinel 與一個主伺服器或者從伺服器建立起訂閱連接之後, sentinel 就會通過訂閱連接,向伺服器發送以下命令:
*/
SUBSCRIBE __sentinel__:hello
/* Now we subscribe to the Sentinels "Hello" channel. */
// 發送 SUBSCRIBE __sentinel__:hello 命令,訂閱頻道
retval = redisAsyncCommand(ri->pc,
sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",
SENTINEL_HELLO_CHANNEL);
當一個Sentinel從_sentinel_:hello頻道收到一條資訊時,Sentinel會對這條資訊進行分析,提取出資訊中 ip 、port、run_id 等8個參數,並進行以下檢查:如果這條消息是自己發的,就直接忽略。如果是新進來的Sentinel , 此時Sentinel 會對 對應的主伺服器實例結構進行更新,即將新加進來的 Sentinel 添加到 sentinels 字典中。
每個Sentinel都有自己的一個sentinels字典,Sentinels字典資訊保存了除自己之外的所有Sentinel資訊。
下線狀態
對於Redis的Sentinel中關於下線有兩個不同的概念:(1)主觀下線(Subjectively Down, 簡稱 Sdown) 指的是單個 Sentinel 實例對伺服器做出的下線判斷,此時不會進行故障轉移。(2) 客觀下線(Objectively Down, 簡稱 Odown)指的是多個 Sentinel 實例在對同一個伺服器做出 Sdown 判斷,此時目標sentinel會對主伺服器進行故障轉移。本篇具體詳細介紹。
主觀下線狀態
默認的Sentinel會以每秒一次的頻率向所有與它創建命令連接的實例(包括主、從、其他sentinel在內)發送PING命令,並通過實例回復來判斷實例是否在線。
合法的回復
+pong 、 -loading、 -masterdown。
無效回復
除此之外的所有回復或者無回復都被視作無效回復。無回復指在指定的時間內沒有回復就認為是無回復。
down-after-milliseconds # 指定的時間 未收到回復 視為無效
用戶設置down-after-milliseconds選項的值,不僅會被sentinel用來判斷主伺服器的主觀下線狀態,還會被用於判斷主伺服器下的所有從伺服器,以及同樣監視主伺服器的其他sentinel的主觀下線狀態。
-- 例如用戶向sentinel設置以了下配置:
sentinel monitor master 127.0.0.1 6379 2
sentinel down-after-milliseconds master 50000
這裡的master是主伺服器的名稱, 埠默認6379 ,2代表sentinel集群中有2個sentinel認為master 狀態下線時,才能真正認為該master已經不可用了(也就是客觀下線)。
這50000毫秒不僅會成為sentinel判斷master進入主觀下線的標準,還會判斷所有從庫、其它sentinel進入主觀下線的標準。
當多個sentinel設置的主觀下線時長可能不同
對於多個sentinel共同監視同一個主伺服器時,這些sentinel在配置文件sentinle.conf中所設置的down-after-milliseconds值也可能不同,因此當一個sentinel將主伺服器判斷為主觀下線時,其它sentinel可能仍然會認為主伺服器處於在線狀態。只有全部的sentine都判斷進入了主觀下線狀態時,才會認為主master進入了主觀下線狀態。
客觀下線狀態
當Sentinel將一個主伺服器判斷為主觀下線之後,為了確認這個主伺服器是否真的下線了,會向同樣監視這一主伺服器的其它Sentinel進行詢問,當有半數以上(看具體配置, 一般的是半數以上 例如sentinel monitor mymaster 127.0.0.1 6379 2 中 就為當 2 個判定下線時,就認為時客觀下線了)
當master, 被確定客觀下線之後sentinel 們 會選出一個 決策者 去執行故障轉移操作。客觀下線條件只適用於主伺服器。
is-master-down-by-addr命令用來判斷是否客觀下線
sentinel is-master-down-by-addr ip port current_epoch run_id
sentinel當前的配置紀元 current_epoch 用於選舉 決策者 sentinel, run_id可以是*或者sentinel的 運行id。
決策者選取
假設現在有4個sentinel 這四個sentinel 既是投票者,也是候選者(這四個必須時健康的)。
1 不能有下面三個標記中的一個:SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED
2 ping 心跳正常
3 優先順序不能為 0(slave->slave_priority)
4 INFO 數據不能超時
5 主從連接斷線會時間不能超時
投票的過程很簡單,每個sentinel 都將自己的ip、 port、current_epoch、run_id 由 is-master-down 發送到 hello 頻道。
sentinel 第一個獲取到誰的 is-master-down 資訊, 就將自己的票投給對應的sentinel。
一次過後 current_epoch 最大的,且超過了半數以上。則被選為決策者 否則再來一輪,每增加一輪 current_epoch + 1, 直到選出為止。
故障轉移
選取候選Slave
1 在線的
2 響應速度快的
3 與原 master 斷開連接最短的
4 優先原則
優先順序>offset>runid
最終選取出 新的 master 之後向新的 master 發送
slaveof no one # 斷開主從
然後聲明新的master
slaveof ip port # 發送新的IP 和 新的port
最後將原來的 master 作為從機。當重新上線時,sentinel 會發送 salveof 命令使其成為從機。
總結
-
sentinel只是一個運行在特殊模式下的redis伺服器,它使用了和普通模式不同的命令表,以及區別與普通模式下使用的命令不同。 -
sentinel向主伺服器發送INFO命令來獲得主伺服器屬下所有從伺服器的地址資訊,並為這些從伺服器創建相應的實例結構,以及連向這些從伺服器的命令連接和訂閱連接。 -
一般情況下,
sentinel以每10秒一次的頻率向被監視的主伺服器和從伺服器發送INFO命令,當主伺服器處於下線狀態,或者sentinel正在對主伺服器進行故障轉移操作時,sentinel向從伺服器發送INFO命令的頻率會改為1秒一次。 -
對於監視同一個主伺服器和從伺服器的多個
sentinel來說,它們會以每2秒一次的頻率,通過向被監視的_sentinel_:hello頻道發送消息來向其他sentinel宣告自己的存在。 -
每個
sentinel也會從_sentinel_:hello中頻道中接收其他sentinel發來的資訊,並根據這些資訊為其他sentinel創建相應的實例結構,以及命令連接。 -
sentinel只會與主伺服器和從伺服器創建命令連接和訂閱連接,sentinel與sentinel之間則只創建命令連接。 -
sentinel以每秒一次的頻率向實例(包括主,從,其它sentinel)發送PING命令,並根據實例的回復來判斷實例是否在線,當一個實例在指定的時長中連續向sentinel發送無效回復時,sentinel會將這個實例判斷為主觀下線。 -
當
sentinel將一個主伺服器判斷為主觀下線時,它會向同樣的監視這個主伺服器的其他sentinel進行詢問,看它們是否同意這個主伺服器已經進入主觀下線狀態。 -
當
sentinel收集到足夠多的主觀下線投票之後,它會將主伺服器判斷為客觀下線,並發起一次針對主伺服器的故障轉移操作。

