redis系列:哨兵
1 簡介
Sentinel(哨兵)是Redis 的高可用性解決方案:通過哨兵可以創建一個當主服務器出現故障時自動將從服務器升級為主服務器的一個分布式系統。解決了主從復制出現故障時需要人為幹預的問題。
這篇介紹哨兵的搭建,以及哨兵是如何進行哨兵發現和主從切換等功能。
2 準備工作
在原先主從的基礎上,每臺機器啟動一個哨兵。架構圖如下
2.1 配置
配置文件如下
daemonize yes bind 0.0.0.0 port 26379 dir "/usr/soft/redis" loglevel notice logfile "/usr/soft/redis/sentinel.log" # 修改改成5秒 sentinel monitor learnSentinelMaster 192.168.17.101 6379 2 sentinel down-after-milliseconds learnSentinelMaster 5000 sentinel config-epoch learnSentinelMaster 1
2.2 啟動方式
有兩種方式
src/redis-sentinel sentinel.conf
src/redis-server sentinel.conf --sentinel
3 開始搭建
哨兵搭建的過程如下
哨兵集群搭建完畢後,日誌內容如下
啟動後配置文件sentinel.conf會增加內容
daemonize yes bind 0.0.0.0 port 26379 dir "/usr/soft/redis" loglevel notice logfile "/usr/soft/redis/sentinel.log" # 修改改成5秒 sentinel myid b457cbbcda1991f540d56c6e8faea123a668b16c sentinel monitor learnSentinelMaster 192.168.17.101 6379 2 sentinel down-after-milliseconds learnSentinelMaster 5000 # Generated by CONFIG REWRITE sentinel config-epoch learnSentinelMaster 1 sentinel leader-epoch learnSentinelMaster 0 sentinel known-slave learnSentinelMaster 192.168.17.102 6379 sentinel known-slave learnSentinelMaster 192.168.17.103 6379 sentinel known-sentinel learnSentinelMaster 192.168.17.101 26379 f0230d4fdf1ffc7865852de71f16b3017cc1617c sentinel known-sentinel learnSentinelMaster 192.168.17.102 26379 5b1099513713310eba94e69513dba76cf0ac2222 sentinel current-epoch 1
4 啟動流程
接下來看看哨兵集群啟動過程中,Redis內部發生了什麽。步驟如下
- 初始化服務器
- 使用Sentinel專用代碼
- 初始化Sentinel狀態
- 創建連向主服務器的網絡連接
4.1 初始化服務器
Sentinel 本質上只是一個運行在特殊模式下的Redis服務器,所以初始化時和不同的Redis服務器初始化沒什麽較大的區別。有區別的就是哨兵服務器並不會載入RDB文件和AOF文件,還有一些命令功能哨兵服務器不使用。
4.2 使用Sentinel專用代碼
初始化服務器之後,哨兵服務器會將一部分普通Redis的服務器使用的代碼替換成哨兵專用的代碼。以下就是哨兵的命令列表,代碼文件在https://github.com/antirez/redis/blob/unstable/src/sentinel.c
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},
{"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};
4.3 初始化Sentinel狀態
在應用了哨兵專用的代碼之後,哨兵會初始化狀態,這個哨兵狀態結構包含了服務器中所有和哨兵功能有關的狀態。結構體代碼位置在也在entinel.c文件中,結構體代碼如下
/* Main state. */
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* 當前哨兵ID. */
uint64_t current_epoch; /* 當前紀元. */
dict *masters; /* 存放哨兵監視的主服務器,key是主服務器的名字,value是指向主服務器的指針tances. */
int tilt; /* 是否處於TILT模式? */
int running_scripts; /* 目前執正在執行的腳本數量 */
mstime_t tilt_start_time; /* 進入TITL開始的時間 */
mstime_t previous_time; /* 最後一次執行時間處理器的時間*/
list *scripts_queue; /* 包含了所有需要執行的用戶腳本 */
char *announce_ip; /* 當配置文件中的announce_ip不為空時,記錄著這些IP地址 */
int announce_port; /* 配置文件中的announce_port */
unsigned long simfailure_flags; /* 故障模擬 */
int deny_scripts_reconfig; /* 是否允許哨兵在運行時修改腳本位置? */
} sentinel;
啟動哨兵出現的日誌如下
# oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
# Redis version=4.9.103, bits=64, commit=00000000, modified=0, pid=2100, just started
# Configuration loaded
* Increased maximum number of open files to 10032 (it was originally set to 1024).
* Running mode=sentinel, port=26379.
# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
哨兵id如下
# Sentinel ID is b457cbbcda1991f540d56c6e8faea123a668b16c
4.4 創建連向主服務器的網絡連接
初始化哨兵的最後一步是創建連向被監視的主服務器的網絡連接,哨兵將會成為主服務器的客戶端。哨兵會向主服務器創建兩個異步網絡連接
- 命令連接,用於向主服務器發送命令,並接受命令。
- 訂閱連接,專門用於訂閱主服務器的_sentinel_:hello頻道。
啟動哨兵過程到這裏就結束了,接下來將進入下個環節。
5 獲取信息
獲取信息階段會獲取主服務器信息和從服務器信息以及哨兵的相關信息。
5.1 獲取主服務器信息
哨兵默認會以10s一次的頻率,發送命令連接向被監視的主服務器發送INFO命令,並通過分析INFO命令的回復來獲取主服務器的當前信息。
監控主服務器
# +monitor master learnSentinelMaster 192.168.17.101 6379 quorum 2
通過分析主服務器返回的信息,可以獲取到兩方面的信息
- 主服務器本身的信息
- 從服務器的信息
獲取到從服務器信息之後,哨兵會更新保存主服務器實例結構的slaves字典。
5.2 獲取從服務器信息
當哨兵發現主服務器有新的從服務器出現時,哨兵會為這個新的從服務器創建相應的實例結構之外,還會創建到從服務器的命令連接和訂閱連接。
發現新的從服務器會出現如下日誌
* +slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
在創建命令連接之後,會發送INFO命令獲取信息。通過從服務器回復的信息中,可以獲得以下內容
- 從服務器的運行ID run_id
- 從服務器的角色 role
- 從服務器的IP地址 master_host,以及主服務器的端口號master_port
- 從服務器的連接狀態 matser_link_status
- 從服務器的優先級 salve_pripority
- 從服務器的復制偏移量 slave_repl_offest
獲取到這些信息之後,會對之前創建的從服務器實例結構進行更新。
5.3 獲取其他Sentinel的信息
在獲取其他哨兵的信息之前,先要知道向主服務器和從服務器發送信息
和接收來自主服務器和從服務器的頻道信息
。
5.3.1 向主服務器和從服務器發送信息
發送的命令格式如下
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
PUBLISH是發布消息的命令,__sentinel__:hello
是頻道的名稱,後面就是一些參數,參數信息如下
5.3.2 接收來自主服務器和從服務器的頻道信息
當Sentinel與一個主服務器或者從服務器建立起訂閱連接之後,Sentinel就會通過訂閱連接,向服務器發送命令:
SUBSCRIBE __sentinel__:hello
表示哨兵訂閱__sentinel__:hello
這個頻道,接收這個頻道的消息。
其他哨兵可以通過接收這個頻道的消息來發現其他哨兵的存在。
5.3.3 發現哨兵
通過接收__sentinel__:hello
頻道的消息可以發現其他哨兵的存在。當哨兵接收到一條來自__sentinel__:hello
頻道的消息時,會出現下方
- 判斷該消息是否是自己發送的,是則忽略這條消息
- 消息不是自己發送時,說明有新的哨兵
- 查看自己是否存有該哨兵的信息,有則更新該哨兵的信息
- 沒有則創建一個新的哨兵實例結構,並保存到sentinels字典中
註:sentinels字典是專門保存哨兵信息的
5.3.4 創建連向其他哨兵的命令連接
當Sentinel通過頻道信息發現一個新的Sentinel時,不僅會在自身的sentinels字典中為新Sentinel創建實例結構,還會創建一個連向新Sentinel的命令連接,同時新的Sentinel也會創建一個連向這個Sentinel的命令連接,最終多個Sentinel將形成一個互相連接的網絡。
註:哨兵之間不會創建訂閱連接
發現哨兵的日誌如下
* +sentinel sentinel f0230d4fdf1ffc7865852de71f16b3017cc1617c 192.168.17.101 26379 @ learnSentinelMaster 192.168.17.101 6379
* +sentinel sentinel 5b1099513713310eba94e69513dba76cf0ac2222 192.168.17.102 26379 @ learnSentinelMaster 192.168.17.101 6379
6 模擬101主服務器掉錢
模擬101主服務器掉錢的過程如下
斷線重連的日誌內容如下
接下來開始分析斷線過程中的每一步驟
- 檢測主觀下線狀態
- 檢測客觀下線狀態
- 選舉領頭哨兵
- 故障轉移
6.1 檢測主觀下線狀態
在默認情況下,Sentinel會以每秒一次的頻率向所有與它創建了命令連接的實例(主,從,其他Sentinel)發送 PING命令 ,通過判斷返回的內容來判斷是否在線,命令分為有效回復和無效回復兩種。
- 有效回復
- +PING
- -LOADING
- -MASTERDOWN
- 無效回復
- 除有效回復以外的內容
- 指定時間內沒有回復
配置文件中的down-after-milliseconds參數可以設置指定時間,在這個時間段內沒有收到回復則判定該服務器處於主觀下線狀態。
sentinel down-after-milliseconds learnSentinelMaster 5000
現在101客戶端上輸入以下命令,讓服務器睡眠30秒
debug sleep 30
此時查看哨兵日誌,等待5秒後出現以下內容
# +sdown master learnSentinelMaster 192.168.17.101 6379
6.2 檢測客觀下線狀態
當一個哨兵將一個主服務器判斷為主觀下線之後,會向其他監視該主服務器的哨兵進行詢問,當有足夠數量的哨兵判定主服務器下線時,會執行故障轉移操作 。
註:這裏不對哨兵之間互相發送的消息進行說明
在配置中可以決定判定主服務器進入客觀下線狀態所需要的服務器數量,下方配置的最後一個參數就是所需的哨兵數量,這裏填寫的是2
sentinel monitor learnSentinelMaster 192.168.17.101 6379 2
下面的日誌說明了主服務器101已經進入客觀下線狀態
# +odown master learnSentinelMaster 192.168.17.101 6379 #quorum 2/2
當前紀元被更新 ,試圖故障恢復
# +new-epoch 2
# +try-failover master learnSentinelMaster 192.168.17.101 6379
此時開始準備選舉領頭哨兵進行故障轉移
6.3 選舉領頭哨兵
當主服務器被判定為客觀下線之後,各個哨兵服務器將會選舉出一個領頭哨兵,有這個領頭哨兵對下線服務器進行故障轉移操作,選舉領頭哨兵的規則如下:
- 所有在線的Sentinel都有被選為領頭Sentinel的資格;
- 每次進行選舉之後,不論選舉是否成功,所有Sentinel的配置紀元都會自增一次;
- 在一個配置紀元裏,所有Sentinel都有一次將某個Sentinel設置為局部領頭Sentinel的機會,並且局部領頭一旦設置,在這個配置紀元裏就不會再更改;
- 每個發現主服務器進入客觀下線的Sentinel都會要求其他Sentinel將自己設置為局部領頭Sentinel;
- 當一個Sentinel向另一個Sentinel發送請求命令,並且命令中的runid不是*而是運行id時,這表示源Sentinel要求目標Sentinel將前者設置為後者的局部領頭Sentinel。
- 設置局部領頭Sentinel的原則是先到先得,之後所有的設置要求都會被拒絕;
- 目標Sentinel在收到命令後,會返回一條回復,回復中的leader_runid參數和leader_epoch參數分別記錄了目標Sentinel的局部領頭Sentinel的運行ID和配置紀元;
- 源Sentinel在收到回復後,會檢查配置紀元與自己是否相等,如果相同,且leader_runid與自己相同,那麽表示自己成為了目標的局部領頭;
- 如果有某個Sentinel被半數以上的Sentinel設置成了局部領頭Sentinel,那麽它成為領頭Sentinel;
- 因為領頭的產生需要半數哨兵的支持,並且每個哨兵在每個配置紀元只能設置一次局部領頭Sentinel,所以在一個配置紀元裏面,只會出現一個領頭Sentinel;
- 如果在給定時限內沒有選出領頭Sentinel,那麽各個Sentinel將在一段時間之後再次進行選舉,直到選出來。
下方就是選舉領頭哨兵的日誌內容
# +vote-for-leader b457cbbcda1991f540d56c6e8faea123a668b16c 2
# 5b1099513713310eba94e69513dba76cf0ac2222 voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2
# f0230d4fdf1ffc7865852de71f16b3017cc1617c voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2
6.4 故障轉移
在選舉出領頭哨兵之後,領頭哨兵需要執行故障轉移操作,操作主要分為三個步驟
- 選出新的主服務器
- 修改從服務器的復制目標
- 將舊的主服務器變為從服務器
6.4.1 選出新的主服務器
此時,領頭哨兵需要選出新的主服務器,然後向新的主服務器發送SLAVEOF no one命令,將這個從服務器轉換為主服務器。
選擇過程會過濾掉不符合要求的服務器:
- 處於下線或者斷線狀態的從服務器
- 最近5秒內沒有回復過領頭哨兵的INFO信息的從服務器
- 與已下線主服務器連接斷開超過(down-after-milliseconds * 10)毫秒的從服務器。(與主服務器客觀下線時間進行比較)
新的主服務器只選擇通過上面的測試,並在上面的標準基礎上排序:
- Slave通過Redis實例的redis.conf文件配置的slave-priority排序。優先級越低越被優先考慮。
- 如果優先級相同,檢查slave的復制偏移量,並選擇接收更多數據的slave。
- 如果多個slave有相同的優先級和同樣的處理數據過程,就會執行一個更進一步的驗證,選擇一個有較短run ID的slave。run ID 對於 slave沒太大用,但是非常有助於選擇slave的過程,而不是隨機選擇slave。
選出合適的從節點作為新的主節點
2101:X 31 Jul 19:13:35.709 # +failover-state-select-slave master learnSentinelMaster 192.168.17.101 6379
2101:X 31 Jul 19:13:35.793 # +selected-slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
開始講102轉換為主節點
* +failover-state-send-slaveof-noone slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
* +failover-state-wait-promotion slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
# +promoted-slave slave 192.168.17.102:6379 192.168.17.102 6379 @ learnSentinelMaster 192.168.17.101 6379
# +failover-state-reconf-slaves master learnSentinelMaster 192.168.17.101 6379
6.4.2 修改從服務器的復制目標
當新的主服務器出現之後,領頭哨兵會向其他從服務器發送slaveof 命令去復制新的主服務器。
下方記錄了領頭哨兵向從服務器發送 SALVEOF命令去復制新的主服務器。
* +slave-reconf-sent slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave-reconf-inprog slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
* +slave-reconf-done slave 192.168.17.103:6379 192.168.17.103 6379 @ learnSentinelMaster 192.168.17.101 6379
6.4.3 將舊的主服務器變為從服務器
這時候如果下線的主服務器重啟上線了怎麽辦?這也是故障轉移要做的最後一步,將已下線的主服務器設置為新的主服務器的從服務器。當下線的主服務器重新上線時,哨兵就會向它發送SLAVEOF命令,讓他成為新的主服務器的從服務器。
此時101服務器上線
# -odown master learnSentinelMaster 192.168.17.101 6379
故障轉移成功完成。所有slaves被重新配置為新master的從
# +failover-end master learnSentinelMaster 192.168.17.101 6379
# +switch-master learnSentinelMaster 192.168.17.101 6379 192.168.17.102 6379
轉換101狀態
* +slave slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
# +sdown slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
# -sdown slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
* +convert-to-slave slave 192.168.17.101:6379 192.168.17.101 6379 @ learnSentinelMaster 192.168.17.102 6379
這時候再次查看配置文件會發現多了一行sentinel current-epoch 2
#後臺啟動
daemonize yes
bind 0.0.0.0
port 26379
dir "/usr/soft/redis"
loglevel notice
logfile "/usr/soft/redis/sentinel.log"
# 修改改成5秒
sentinel myid f0230d4fdf1ffc7865852de71f16b3017cc1617c
sentinel monitor learnSentinelMaster 192.168.17.102 6379 2
sentinel down-after-milliseconds learnSentinelMaster 5000
# Generated by CONFIG REWRITE
sentinel config-epoch learnSentinelMaster 2
sentinel leader-epoch learnSentinelMaster 2
sentinel known-slave learnSentinelMaster 192.168.17.103 6379
sentinel known-slave learnSentinelMaster 192.168.17.101 6379
sentinel known-sentinel learnSentinelMaster 192.168.17.103 26379 b457cbbcda1991f540d56c6e8faea123a668b16c
sentinel known-sentinel learnSentinelMaster 192.168.17.102 26379 5b1099513713310eba94e69513dba76cf0ac2222
sentinel current-epoch 2
7 相關配置
# Example sentinel.conf
# 綁定IP地址
# bind 127.0.0.1 192.168.1.1
# 保護模式(是否禁止外部鏈接,除綁定的ip地址外)
# protected-mode no
# 當前Sentinel服務運行的端口
port 26379
#
# sentinel announce-ip <ip>
# sentinel announce-port <port>
# Sentinel服務運行時使用的臨時文件夾
dir /tmp
# 監聽地址為ip:port的一個master
sentinel monitor mymaster 127.0.0.1 6379 2
# 設置連接master和slave時的密碼,註意的是sentinel不能分別為master和slave設置不同的密碼,因此master和slave的密碼應該設置相同。
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定了Sentinel認為Redis實例已經失效所需的毫秒數
sentinel down-after-milliseconds mymaster 30000
# 指定了在執行故障轉移時,最多可以有多少個從Redis實例在同步新的主實例,在從Redis實例較多的情況下這個數字越小,同步的時間越長,完成故障轉移所需的時間就越長
sentinel parallel-syncs mymaster 1
# 如果在該時間(ms)內未能完成故障轉移操作,則認為該故障轉移失敗
sentinel failover-timeout mymaster 180000
# 指定sentinel檢測到該監控的redis實例指向的實例異常時,調用的報警腳本。該配置項可選,但是很常用
# sentinel notification-script mymaster /var/redis/notify.sh
# 當一個master由於failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已經發生改變的信息。
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
哨兵的配置文件:https://github.com/rainbowda/learnWay/tree/master/learnRedis/sentinel,有需要的可以下載。
redis系列:哨兵