哨兵(sentinel) 是一個分佈式系統,是程序高可用性的一個保障。用於監視任意多個主服務器,以及這些主服務器屬下的全部從服務器,當出現故障時經過投票機制選擇新的master並將全部slave鏈接到新的master。html
不斷地檢查master和slave是否正常運行 master存活檢測、master與slave運行狀況檢測。python
當被監控地服務器出現問題時,向其餘(哨兵間,客戶端)發送通知。redis
斷開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
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 會根據這個配置進行相應的更新)。
從下面啓動代碼能夠看出啓動方式由函數 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
兩個初始化函數。接下來看看這兩個函數都幹了什麼~ 。
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} };
在完成命令表加載以後,緊接着會進行 sentinelState
和 sentinelRedisInstance
結構的一個初始化。
Sentinel
狀態中的 masters
字典記錄了全部被監視的主服務器信息,鍵爲服務器名字,值爲被監視主服務器對應的sentinel.c/sentinelRedisInstance
結構。每一個sentinelRedisInstance
實例結構表明監視一個Redis
服務器實例,這個實例能夠是主服務器,也能夠是從服務器,或者另一個sentinel服務器。
對於sentinelState
的初始化將引起對masters
字典的初始化,而masters字典的初始化是根據被該入的sentinel
配置文件(sentinel_26379.conf
)來進行的。主要爲被監控 master
的ip
和 port
。
注意 這些都是有 sentinel 來維護和使用的。
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;
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
, 直到選出爲止。
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
收集到足夠多的主觀下線投票以後,它會將主服務器判斷爲客觀下線,併發起一次針對主服務器的故障轉移操做。