Redis 哨兵模式

**Keeper 導讀:**我們知道 Reids 提供了主從模式的機制,來保證可用性,可是如果主庫發生故障了,那就直接會影響到從庫的同步,怎麼辦呢?

所以,如果主庫掛了,我們就需要運行一個新主庫,比如說把一個從庫切換爲主庫,把它當成主庫。這就涉及到三個問題:

圍繞這 3 個問題,我們來看下不需要人工干預就可以解決這三個問題的 Redis 哨兵。圖片

一、Redis Sentinel 哨兵

上圖 展示了一個典型的哨兵架構圖,它由兩部分組成,哨兵節點和數據節點:

在複製的基礎上,哨兵實現了 自動化的故障恢復 功能,下面是官方對於哨兵功能的描述:

二、 Hello Wolrd

2.1 部署主從節點

下面分別是主節點(port=6379)和 2 個從節點(port=6380、6381)的配置文件:

#redis.conf  master
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"

#redis_6380.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
replicaof 127.0.0.1 6379

#redis_6381.conf
port 6381
daemonize yes
logfile "6381.log"
dbfilename "dump-6381.rdb"
replicaof 127.0.0.1 6379

然後我們可以執行 redis-server 來根據配置文件啓動不同的 Redis 實例,依次啓動主從節點:

redis-server redis.conf
redis-server redis_6380.conf 
redis-server redis_6381.conf

節點啓動後,我們執行 redis-cli 默認連接到我們端口爲 6379 的主節點執行 info Replication 檢查一下主從狀態是否正常:(可以看到下方正確地顯示了兩個從節點)

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=154,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=140,lag=1
master_replid:52a58d69125881d3af366d0559439377a70ae879
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:154
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:154

2.2 部署哨兵節點

按照上面同樣的方法,我們給哨兵節點也創建三個配置文件。(哨兵節點本質上是特殊的 Redis 節點,所以配置幾乎沒什麼差別,只是在端口上做區分就好,每個哨兵只需要配置監控主節點,就可以自動發現其他的哨兵節點和從節點)

# redis-sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2

# redis-sentinel-26380.conf
port 26380
daemonize yes
logfile "26380.log"
sentinel monitor mymaster 127.0.0.1 6379 2

# redis-sentinel-26381.conf
port 26381
daemonize yes
logfile "26381.log"
sentinel monitor mymaster 127.0.0.1 6379 2

其中,sentinel monitor mymaster 127.0.0.1 6379 2 配置的含義是:該哨兵節點監控 127.0.0.1:6379 這個主節點,該主節點的名稱是 mymaster,最後的 2 的含義與主節點的故障判定有關:至少需要 2 個哨兵節點同意,才能判定主節點故障並進行故障轉移。

啓動 3 個哨兵節點:

redis-sentinel redis-sentinel-26379.conf
redis-sentinel redis-sentinel-26380.conf
redis-server redis-sentinel-26381.conf --sentinel   #等同於 redis-sentinel redis-sentinel-26381.conf

使用 redis-cil 工具連接哨兵節點,並執行 info Sentinel 命令來查看是否已經在監視主節點了:

redis-cli -p 26380 
127.0.0.1:26380> info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

此時你打開剛纔寫好的哨兵配置文件,你還會發現出現了一些變化。

2.3 演示故障轉移

我們先看下我們啓動的 redis 進程,3 個數據節點,3 個哨兵節點

使用 kill 命令來殺掉主節點,同時 在哨兵節點中執行 info Sentinel 命令來觀察故障節點的過程:

如果 剛殺掉瞬間 在哨兵節點中執行 info 命令來查看,會發現主節點還沒有切換過來,因爲哨兵發現主節點故障並轉移需要一段時間:

# 第一時間查看哨兵節點發現並未轉移,還在 6379 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

一段時間之後你再執行 info 命令,查看,你就會發現主節點已經切換成了 6381 端口的從節點:

# 過一段時間之後在執行,發現已經切換了 6381 端口
127.0.0.1:26379> info Sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6381,slaves=2,sentinels=3

但同時還可以發現,哨兵節點認爲新的主節點仍然有兩個從節點 (上方 slaves=2),這是因爲哨兵在將 6381 切換成主節點的同時,將 6379 節點置爲其從節點。雖然 6379 從節點已經掛掉,但是由於 哨兵並不會對從節點進行客觀下線,因此認爲該從節點一直存在。當 6379 節點重新啓動後,會自動變成 6381 節點的從節點。

另外,在故障轉移的階段,哨兵和主從節點的配置文件都會被改寫:

三、哨兵機制的工作流程

其實哨兵主要負責的就是三個任務:監控選主通知

在監控和選主過程中,哨兵都需要做一些決策,比如

這就引出了兩個概念,“主觀下線” 和 “客觀下線”

3.1 主觀下線和客觀下線

我先解釋下什麼是 “主觀下線”。

哨兵進程會使用 PING 命令檢測它自己和主、從庫的網絡連接情況,用來判斷實例的狀態。如果哨兵發現主庫或從庫對 PING 命令的響應超時了,那麼,哨兵就會先把它標記爲 “主觀下線”。

如果檢測的是從庫,那麼,哨兵簡單地把它標記爲 “主觀下線” 就行了,因爲從庫的下線影響一般不太大,集羣的對外服務不會間斷。

但是,如果檢測的是主庫,那麼,哨兵還不能簡單地把它標記爲 “主觀下線”,開啓主從切換。因爲很有可能存在這麼一個情況:那就是哨兵誤判了,其實主庫並沒有故障。可是,一旦啓動了主從切換,後續的選主和通知操作都會帶來額外的計算和通信開銷。

爲了避免這些不必要的開銷,我們要特別注意誤判的情況。

誤判一般會發生在集羣網絡壓力較大、網絡擁塞,或者是主庫本身壓力較大的情況下。一旦哨兵判斷主庫下線了,就會開始選擇新主庫,並讓從庫和新主庫進行數據同步,這個過程本身就會有開銷,例如,哨兵要花時間選出新主庫,從庫也需要花時間和新主庫同步。

那怎麼減少誤判呢?

在日常生活中,當我們要對一些重要的事情做判斷的時候,經常會和家人或朋友一起商量一下,然後再做決定。

哨兵機制也是類似的,它通常會採用多實例組成的集羣模式進行部署,這也被稱爲哨兵集羣。引入多個哨兵實例一起來判斷,就可以避免單個哨兵因爲自身網絡狀況不好,而誤判主庫下線的情況。同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。

在判斷主庫是否下線時,不能由一個哨兵說了算,只有大多數的哨兵實例,都判斷主庫已經 “主觀下線” 了,主庫纔會被標記爲“客觀下線”,這個叫法也是表明主庫下線成爲一個客觀事實了。這個判斷原則就是:少數服從多數。同時,這會進一步觸發哨兵開始主從切換流程。

需要特別注意的是,客觀下線是主節點纔有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。

3.2 選舉領導者哨兵節點

當主節點被判斷客觀下線以後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。

監視該主節點的所有哨兵都有可能被選爲領導者,選舉使用的算法是 Raft 算法;Raft 算法的基本思路是先到先得:即在一輪選舉中,哨兵 A 向 B 發送成爲領導者的申請,如果 B 沒有同意過其他哨兵,則會同意 A 成爲領導者。(Raft 算法:https://raft.github.io/)

3.3 故障轉移

接着,選舉出的領導者哨兵,開始故障轉移操作,大概分 3 步:

  1. 第一步要做的就是在已下線主服務器屬下的所有從服務器中,挑選出一個狀態良好、數據完整的從服務器

  2. 第二步,更新主從狀態,向選出的從服務器發送 slaveof no one 命令,將這個從服務器轉換爲主服務器,並通過 slaveof 命令讓其他節點成爲其從節點

  3. 第三步將已下線的主節點設置爲從節點

細說下第一步的選主過程

一般來說,我把哨兵選擇新主庫的過程稱爲 “篩選 + 打分”。

篩選就是先過濾掉不健康的從節點,那些被標記爲主觀下線、已斷線、或者最後一次回覆 PING 命令的時間大於五秒鐘的從服務器都會被 淘汰

打分就是按 Redis 給定的三個規則,給剩下的從庫逐個打分,將得分最高的從庫選爲新主庫,這個規則分別是:

四、哨兵集羣的原理

實際上,一旦多個實例組成了哨兵集羣,即使有哨兵實例出現故障掛掉了,其他哨兵還能繼續協作完成主從庫切換的工作,包括判定主庫是不是處於下線狀態,選擇新主庫,以及通知從庫和客戶端。

認真看到這裏的話,應該會有個疑問,我們在 hello world 環節中只是在哨兵節點加了一條配置

sentinel monitor mymaster 127.0.0.1 6379 2

怎麼就能組成一個哨兵集羣呢?

一套合理的監控機制是哨兵節點判定節點不可達的重要保證,Redis 哨兵通過三個定時監控任務完成對各個節點發現和監控:

  1. 每隔 10 秒,每個哨兵節點會向主節點和從節點發送 info 命令獲取最新的拓撲結構

    這個定時任務的作用具體可以表現在三個方面:

  1. 每隔 2 秒,每個哨兵節點會向 Redis 數據節點的 sentinel:hello 頻道上發送該哨兵節點對於主節點的判斷以及當前哨兵節點的信息,同時每個哨兵節點也會訂閱該頻道,來了解其他哨兵節點以及它們對主節點的判斷。

    這個定時任務可以完成以下兩個工作:

  1. 每隔 1 秒,每個哨兵節點會向主節點、從節點、其餘哨兵節點發送一條 ping 命令做一次心跳檢測,來確認這些節點當前是否可達。

    通過這個定時任務,哨兵節點對主節點、從節點、其餘哨兵節點都建立起連接,實現了對每個節點的監控,這個定時任務是節點失敗判定的重要依據

4.1 基於 pub/sub 機制的哨兵集羣組成

哨兵實例之間可以相互發現,要歸功於 Redis 提供的 pub/sub 機制,也就是 發佈 / 訂閱 機制。

哨兵只要和主庫建立起了連接,就可以在主庫上發佈消息了,比如說發佈它自己的連接信息(IP 和端口)。同時,它也可以從主庫上訂閱消息,獲得其他哨兵發佈的連接信息。當多個哨兵實例都在主庫上做了發佈和訂閱操作後,它們之間就能知道彼此的 IP 地址和端口。

在主從集羣中,主庫上有一個名爲 "**_ _sentinel_ _:hello**" 的頻道,不同哨兵就是通過它來相互發現,實現互相通信的。

舉個例子,具體說明一下。

在下圖中,哨兵 sentinel_26379 把自己的 IP(127.0.0.1)和端口(26379)發佈到頻道上,哨兵 26380 和 26381 訂閱了該頻道。那麼此時,其他哨兵就可以從這個頻道直接獲取哨兵 sentinel_26379 的 IP 地址和端口號。通過這個方式,各個哨兵之間就可以建立網絡連接,哨兵集羣就形成了。它們相互間可以通過網絡連接進行通信,比如說對主庫有沒有下線這件事兒進行判斷和協商。

4.2 哨兵和從庫的連接

哨兵除了彼此之間建立起連接形成集羣外,還需要和從庫建立連接。這是因爲,在哨兵的監控任務中,它需要對主從庫都進行心跳判斷,而且在主從庫切換完成後,它還需要通知從庫,讓它們和新主庫進行同步。

哨兵是如何知道從庫的 IP 地址和端口的呢?

這是由哨兵向主庫發送 INFO 命令來完成的。

就像下圖所示,哨兵 sentinel_26380 給主庫發送 INFO 命令,主庫接受到這個命令後,就會把從庫列表返回給哨兵。接着,哨兵就可以根據從庫列表中的連接信息,和每個從庫建立連接,並在這個連接上持續地對從庫進行監控。senetinel_26379 和 senetinel_26381 可以通過相同的方法和從庫建立連接。

4.3 哨兵和客戶端的連接

但是,哨兵不能只和主、從庫連接。因爲,主從庫切換後,客戶端也需要知道新主庫的連接信息,才能向新主庫發送請求操作。所以,哨兵還需要完成把新主庫的信息告訴客戶端這個任務。

在實際使用哨兵時,我們有時會遇到這樣的問題:如何在客戶端通過監控瞭解哨兵進行主從切換的過程呢?比如說,主從切換進行到哪一步了?這其實就是要求,客戶端能夠獲取到哨兵集羣在監控、選主、切換這個過程中發生的各種事件。此時,我們仍然可以依賴 pub/sub 機制,來幫助我們完成哨兵和客戶端間的信息同步。

從本質上說,哨兵就是一個運行在特定模式下的 Redis 實例,只不過它並不服務請求操作,只是完成監控、選主和通知的任務。所以,每個哨兵實例也提供 pub/sub 機制,客戶端可以從哨兵訂閱消息。

哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關鍵事件。(這裏就不一一列出了)

知道了這些頻道之後,你就可以讓客戶端從哨兵這裏訂閱消息了。具體的操作步驟是,客戶端讀取哨兵的配置文件後,可以獲得哨兵的地址和端口,和哨兵建立網絡連接。然後,我們可以在客戶端執行訂閱命令,來獲取不同的事件消息。

舉個例子,你可以執行如下命令,來訂閱 “所有實例進入客觀下線狀態的事件”:

SUBSCRIBE +odown

當然,你也可以執行如下命令,訂閱所有的事件:

PSUBSCRIBE *

五、小結

Redis 哨兵是 Redis 的高可用實現方案:故障發現、故障自動轉移、配置中心、客戶端通知。

5.1 哨兵機制其實就有三大功能:

5.2 一個哨兵,實際上可以監控多個主節點,通過配置多條 sentinel monitor 即可實現。

5.3 哨兵集羣的關鍵機制:

SentinelRedis 主節點從節點 交互的命令,主要包括:

BZb1jf

SentinelSentinel 交互的命令,主要包括:

oCeCBA

5.4 建議

參考與來源

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/706sq8BenxuxMK8QLUcGBw