redis系列:哨兵

雲梟發表於2018-08-11

1 簡介

Sentinel(哨兵)是Redis 的高可用性解決方案:通過哨兵可以建立一個當主伺服器出現故障時自動將從伺服器升級為主伺服器的一個分散式系統。解決了主從複製出現故障時需要人為干預的問題。
這篇介紹哨兵的搭建,以及哨兵是如何進行哨兵發現和主從切換等功能。

2 準備工作

在原先主從的基礎上,每臺機器啟動一個哨兵。架構圖如下

redis系列:哨兵

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 開始搭建

哨兵搭建的過程如下

redis系列:哨兵

哨兵叢集搭建完畢後,日誌內容如下

redis系列:哨兵

啟動後配置檔案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內部發生了什麼。步驟如下

  1. 初始化伺服器
  2. 使用Sentinel專用程式碼
  3. 初始化Sentinel狀態
  4. 建立連向主伺服器的網路連線

4.1 初始化伺服器

Sentinel 本質上只是一個執行在特殊模式下的Redis伺服器,所以初始化時和不同的Redis伺服器初始化沒什麼較大的區別。有區別的就是哨兵伺服器並不會載入RDB檔案和AOF檔案,還有一些命令功能哨兵伺服器不使用。

4.2 使用Sentinel專用程式碼

初始化伺服器之後,哨兵伺服器會將一部分普通Redis的伺服器使用的程式碼替換成哨兵專用的程式碼。以下就是哨兵的命令列表,程式碼檔案在github.com/antirez/red…

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 建立連向主伺服器的網路連線

初始化哨兵的最後一步是建立連向被監視的主伺服器的網路連線,哨兵將會成為主伺服器的客戶端。哨兵會向主伺服器建立兩個非同步網路連線

  1. 命令連線,用於向主伺服器傳送命令,並接受命令。
  2. 訂閱連線,專門用於訂閱主伺服器的_sentinel_:hello頻道。

啟動哨兵過程到這裡就結束了,接下來將進入下個環節。

5 獲取資訊

獲取資訊階段會獲取主伺服器資訊和從伺服器資訊以及哨兵的相關資訊。

5.1 獲取主伺服器資訊

哨兵預設會以10s一次的頻率,傳送命令連線向被監視的主伺服器傳送INFO命令,並通過分析INFO命令的回覆來獲取主伺服器的當前資訊。

監控主伺服器

# +monitor master learnSentinelMaster 192.168.17.101 6379 quorum 2
複製程式碼

通過分析主伺服器返回的資訊,可以獲取到兩方面的資訊

  1. 主伺服器本身的資訊
  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命令獲取資訊。通過從伺服器回覆的資訊中,可以獲得以下內容

  1. 從伺服器的執行ID run_id
  2. 從伺服器的角色 role
  3. 從伺服器的IP地址 master_host,以及主伺服器的埠號master_port
  4. 從伺服器的連線狀態 matser_link_status
  5. 從伺服器的優先順序 salve_pripority
  6. 從伺服器的複製偏移量 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是頻道的名稱,後面就是一些引數,引數資訊如下

redis系列:哨兵

5.3.2 接收來自主伺服器和從伺服器的頻道資訊

當Sentinel與一個主伺服器或者從伺服器建立起訂閱連線之後,Sentinel就會通過訂閱連線,向伺服器傳送命令:

SUBSCRIBE __sentinel__:hello
複製程式碼

表示哨兵訂閱__sentinel__:hello這個頻道,接收這個頻道的訊息。

其他哨兵可以通過接收這個頻道的訊息來發現其他哨兵的存在。

5.3.3 發現哨兵

通過接收__sentinel__:hello頻道的訊息可以發現其他哨兵的存在。當哨兵接收到一條來自__sentinel__:hello頻道的訊息時,會出現下方

  1. 判斷該訊息是否是自己傳送的,是則忽略這條訊息
  2. 訊息不是自己傳送時,說明有新的哨兵
  3. 檢視自己是否存有該哨兵的資訊,有則更新該哨兵的資訊
  4. 沒有則建立一個新的哨兵例項結構,並儲存到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主伺服器掉錢的過程如下

redis系列:哨兵

斷線重連的日誌內容如下

redis系列:哨兵

接下來開始分析斷線過程中的每一步驟

  1. 檢測主觀下線狀態
  2. 檢測客觀下線狀態
  3. 選舉領頭哨兵
  4. 故障轉移

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 選舉領頭哨兵

當主伺服器被判定為客觀下線之後,各個哨兵伺服器將會選舉出一個領頭哨兵,有這個領頭哨兵對下線伺服器進行故障轉移操作,選舉領頭哨兵的規則如下:

  1. 所有線上的Sentinel都有被選為領頭Sentinel的資格;
  2. 每次進行選舉之後,不論選舉是否成功,所有Sentinel的配置紀元都會自增一次;
  3. 在一個配置紀元裡,所有Sentinel都有一次將某個Sentinel設定為區域性領頭Sentinel的機會,並且區域性領頭一旦設定,在這個配置紀元裡就不會再更改;
  4. 每個發現主伺服器進入客觀下線的Sentinel都會要求其他Sentinel將自己設定為區域性領頭Sentinel;
  5. 當一個Sentinel向另一個Sentinel傳送請求命令,並且命令中的runid不是*而是執行id時,這表示源Sentinel要求目標Sentinel將前者設定為後者的區域性領頭Sentinel。
  6. 設定區域性領頭Sentinel的原則是先到先得,之後所有的設定要求都會被拒絕;
  7. 目標Sentinel在收到命令後,會返回一條回覆,回覆中的leader_runid引數和leader_epoch引數分別記錄了目標Sentinel的區域性領頭Sentinel的執行ID和配置紀元;
  8. 源Sentinel在收到回覆後,會檢查配置紀元與自己是否相等,如果相同,且leader_runid與自己相同,那麼表示自己成為了目標的區域性領頭;
  9. 如果有某個Sentinel被半數以上的Sentinel設定成了區域性領頭Sentinel,那麼它成為領頭Sentinel;
  10. 因為領頭的產生需要半數哨兵的支援,並且每個哨兵在每個配置紀元只能設定一次區域性領頭Sentinel,所以在一個配置紀元裡面,只會出現一個領頭Sentinel;
  11. 如果在給定時限內沒有選出領頭Sentinel,那麼各個Sentinel將在一段時間之後再次進行選舉,直到選出來。

下方就是選舉領頭哨兵的日誌內容

# +vote-for-leader b457cbbcda1991f540d56c6e8faea123a668b16c 2
# 5b1099513713310eba94e69513dba76cf0ac2222 voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2
# f0230d4fdf1ffc7865852de71f16b3017cc1617c voted for b457cbbcda1991f540d56c6e8faea123a668b16c 2
複製程式碼

6.4 故障轉移

在選舉出領頭哨兵之後,領頭哨兵需要執行故障轉移操作,操作主要分為三個步驟

  1. 選出新的主伺服器
  2. 修改從伺服器的複製目標
  3. 將舊的主伺服器變為從伺服器

6.4.1 選出新的主伺服器

此時,領頭哨兵需要選出新的主伺服器,然後向新的主伺服器傳送SLAVEOF no one命令,將這個從伺服器轉換為主伺服器。

選擇過程會過濾掉不符合要求的伺服器:

  1. 處於下線或者斷線狀態的從伺服器
  2. 最近5秒內沒有回覆過領頭哨兵的INFO資訊的從伺服器
  3. 與已下線主伺服器連線斷開超過(down-after-milliseconds * 10)毫秒的從伺服器。(與主伺服器客觀下線時間進行比較)

新的主伺服器只選擇通過上面的測試,並在上面的標準基礎上排序:

  1. Slave通過Redis例項的redis.conf檔案配置的slave-priority排序。優先順序越低越被優先考慮。
  2. 如果優先順序相同,檢查slave的複製偏移量,並選擇接收更多資料的slave。
  3. 如果多個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


複製程式碼

哨兵的配置檔案:github.com/rainbowda/l…,有需要的可以下載。

相關文章