哨兵作用 哨兵(sentinel) 是一個分散式系統,是程式高可用性的一個保障。用於監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,當出現故障時通過投票機制選擇新的master並將所有slave連接到新的master。 監控 不斷地檢查master和slave是否正常運行 master存活 ...
哨兵作用
哨兵(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
收集到足夠多的主觀下線投票之後,它會將主伺服器判斷為客觀下線,併發起一次針對主伺服器的故障轉移操作。