Redis 哨兵模式
**Keeper 導讀:**我們知道 Reids 提供了主從模式的機制,來保證可用性,可是如果主庫發生故障了,那就直接會影響到從庫的同步,怎麼辦呢?
所以,如果主庫掛了,我們就需要運行一個新主庫,比如說把一個從庫切換爲主庫,把它當成主庫。這就涉及到三個問題:
-
主庫真的掛了嗎?
-
該選擇哪個從庫作爲主庫?
-
怎麼把新主庫的相關信息通知給從庫和客戶端呢?
圍繞這 3 個問題,我們來看下不需要人工干預就可以解決這三個問題的 Redis 哨兵。
一、Redis Sentinel 哨兵
上圖 展示了一個典型的哨兵架構圖,它由兩部分組成,哨兵節點和數據節點:
-
哨兵節點: 哨兵系統由一個或多個哨兵節點組成,哨兵節點是特殊的 Redis 節點,不存儲數據;
-
數據節點: 主節點和從節點都是數據節點;
在複製的基礎上,哨兵實現了 自動化的故障恢復 功能,下面是官方對於哨兵功能的描述:
-
監控(Monitoring): 哨兵會不斷地檢查主節點和從節點是否運作正常。
監控是指哨兵進程在運行時,週期性地給所有的主從庫發送 PING 命令,檢測它們是否仍然在線運行。如果從庫沒有在規定時間內響應哨兵的 PING 命令,哨兵就會把它標記爲 “下線狀態”;同樣,如果主庫也沒有在規定時間內響應哨兵的 PING 命令,哨兵就會判定主庫下線,然後開始自動切換主庫的流程。
-
通知(Notification): 當被監控的某個 Redis 服務器出現問題時, 哨兵可以通過 API 向管理員或者其他應用程序發送通知。
在執行通知任務時,哨兵會把新主庫的連接信息發給其他從庫,讓它們執行
replicaof
命令,和新主庫建立連接,並進行數據複製。同時,哨兵會把新主庫的連接信息通知給客戶端,讓它們把請求操作發到新主庫上。 -
自動故障轉移(Automatic failover)/ 選主: 當 主節點 不能正常工作時,哨兵會開始 自動故障轉移操作,它會將失效主節點的其中一個 從節點升級爲新的主節點,並讓其他從節點改爲複製新的主節點。
-
配置提供者(Configuration provider): 客戶端在初始化時,通過連接哨兵來獲得當前 Redis 服務的主節點地址。
當客戶端試圖連接失效的主服務器時, 集羣也會向客戶端返回新主服務器的地址, 使得集羣可以使用新主服務器代替失效服務器。
二、 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
節點的從節點。
另外,在故障轉移的階段,哨兵和主從節點的配置文件都會被改寫:
-
對於主從節點: 主要是
slaveof
配置的變化,新的主節點沒有了slaveof
配置,其從節點則slaveof
新的主節點。 -
對於哨兵節點: 除了主從節點信息的變化,紀元 (epoch) (記錄當前集羣狀態的參數) 也會變化,紀元相關的參數都 +1 了。
三、哨兵機制的工作流程
其實哨兵主要負責的就是三個任務:監控、選主和通知。
在監控和選主過程中,哨兵都需要做一些決策,比如
-
在監控任務中,哨兵需要判斷主庫、從庫是否處於下線狀態
-
在選主任務中,哨兵也要決定選擇哪個從庫實例作爲主庫
這就引出了兩個概念,“主觀下線” 和 “客觀下線”
3.1 主觀下線和客觀下線
我先解釋下什麼是 “主觀下線”。
哨兵進程會使用 PING 命令檢測它自己和主、從庫的網絡連接情況,用來判斷實例的狀態。如果哨兵發現主庫或從庫對 PING 命令的響應超時了,那麼,哨兵就會先把它標記爲 “主觀下線”。
如果檢測的是從庫,那麼,哨兵簡單地把它標記爲 “主觀下線” 就行了,因爲從庫的下線影響一般不太大,集羣的對外服務不會間斷。
但是,如果檢測的是主庫,那麼,哨兵還不能簡單地把它標記爲 “主觀下線”,開啓主從切換。因爲很有可能存在這麼一個情況:那就是哨兵誤判了,其實主庫並沒有故障。可是,一旦啓動了主從切換,後續的選主和通知操作都會帶來額外的計算和通信開銷。
爲了避免這些不必要的開銷,我們要特別注意誤判的情況。
誤判一般會發生在集羣網絡壓力較大、網絡擁塞,或者是主庫本身壓力較大的情況下。一旦哨兵判斷主庫下線了,就會開始選擇新主庫,並讓從庫和新主庫進行數據同步,這個過程本身就會有開銷,例如,哨兵要花時間選出新主庫,從庫也需要花時間和新主庫同步。
那怎麼減少誤判呢?
在日常生活中,當我們要對一些重要的事情做判斷的時候,經常會和家人或朋友一起商量一下,然後再做決定。
哨兵機制也是類似的,它通常會採用多實例組成的集羣模式進行部署,這也被稱爲哨兵集羣。引入多個哨兵實例一起來判斷,就可以避免單個哨兵因爲自身網絡狀況不好,而誤判主庫下線的情況。同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。
在判斷主庫是否下線時,不能由一個哨兵說了算,只有大多數的哨兵實例,都判斷主庫已經 “主觀下線” 了,主庫纔會被標記爲“客觀下線”,這個叫法也是表明主庫下線成爲一個客觀事實了。這個判斷原則就是:少數服從多數。同時,這會進一步觸發哨兵開始主從切換流程。
需要特別注意的是,客觀下線是主節點纔有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。
3.2 選舉領導者哨兵節點
當主節點被判斷客觀下線以後,各個哨兵節點會進行協商,選舉出一個領導者哨兵節點,並由該領導者節點對其進行故障轉移操作。
監視該主節點的所有哨兵都有可能被選爲領導者,選舉使用的算法是 Raft 算法;Raft 算法的基本思路是先到先得:即在一輪選舉中,哨兵 A 向 B 發送成爲領導者的申請,如果 B 沒有同意過其他哨兵,則會同意 A 成爲領導者。(Raft 算法:https://raft.github.io/)
3.3 故障轉移
接着,選舉出的領導者哨兵,開始故障轉移操作,大概分 3 步:
-
第一步要做的就是在已下線主服務器屬下的所有從服務器中,挑選出一個狀態良好、數據完整的從服務器
-
第二步,更新主從狀態,向選出的從服務器發送
slaveof no one
命令,將這個從服務器轉換爲主服務器,並通過slaveof
命令讓其他節點成爲其從節點 -
第三步將已下線的主節點設置爲從節點
細說下第一步的選主過程
一般來說,我把哨兵選擇新主庫的過程稱爲 “篩選 + 打分”。
篩選就是先過濾掉不健康的從節點,那些被標記爲主觀下線、已斷線、或者最後一次回覆 PING 命令的時間大於五秒鐘的從服務器都會被 淘汰。
打分就是按 Redis 給定的三個規則,給剩下的從庫逐個打分,將得分最高的從庫選爲新主庫,這個規則分別是:
-
優先級最高的從庫得分最高
用戶可以通過
slave-priority
配置項,給不同的從庫設置不同優先級。比如,你有兩個從庫,它們的內存大小不一樣,你可以手動給內存大的實例設置一個高優先級。在選主時,哨兵會給優先級高的從庫打高分,如果有一個從庫優先級最高,那麼它就是新主庫了。如果從庫的優先級都一樣,那麼哨兵開始第二輪打分。 -
和舊主庫同步程度最接近的從庫得分高
從庫的
slave_repl_offset
需要最接近master_repl_offset
,即得分最高。 -
ID 號小的從庫得分高
每個實例都會有一個 runid,這個 ID 就類似於這裏的從庫的編號。目前,Redis 在選主庫時,有一個默認的規定:在優先級和複製進度都相同的情況下,ID 號最小的從庫得分最高,會被選爲新主庫。
四、哨兵集羣的原理
實際上,一旦多個實例組成了哨兵集羣,即使有哨兵實例出現故障掛掉了,其他哨兵還能繼續協作完成主從庫切換的工作,包括判定主庫是不是處於下線狀態,選擇新主庫,以及通知從庫和客戶端。
認真看到這裏的話,應該會有個疑問,我們在 hello world 環節中只是在哨兵節點加了一條配置
sentinel monitor mymaster 127.0.0.1 6379 2
怎麼就能組成一個哨兵集羣呢?
一套合理的監控機制是哨兵節點判定節點不可達的重要保證,Redis 哨兵通過三個定時監控任務完成對各個節點發現和監控:
-
每隔 10 秒,每個哨兵節點會向主節點和從節點發送 info 命令獲取最新的拓撲結構
這個定時任務的作用具體可以表現在三個方面:
-
通過向主節點執行 info 命令,獲取從節點的信息,這也是爲什麼哨兵節點不需要顯式配置監控從節點。
-
當有新的從節點加入時都可以立刻感知出來。
-
節點不可達或者故障轉移後,可以通過 info 命令實時更新節點拓撲信息
-
每隔 2 秒,每個哨兵節點會向 Redis 數據節點的 sentinel:hello 頻道上發送該哨兵節點對於主節點的判斷以及當前哨兵節點的信息,同時每個哨兵節點也會訂閱該頻道,來了解其他哨兵節點以及它們對主節點的判斷。
這個定時任務可以完成以下兩個工作:
-
發現新的哨兵節點:通過訂閱主節點的
__sentinel__:hello
瞭解其他的哨兵節點信息,如果是新加入的哨兵節點,將該哨兵節點信息保存起來,並與該哨兵節點創建連接。 -
哨兵節點之間交換主節點的狀態,作爲後面客觀下線以及領導者選舉的依據。
-
每隔 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 哨兵集羣的關鍵機制:
-
哨兵集羣是基於 pub/sub 機制組成的
-
基於 INFO 命令的從庫列表,這可以幫助哨兵和從庫建立連接
-
基於哨兵自身的 pub/sub 功能,這實現了客戶端和哨兵之間的事件通知
Sentinel
與 Redis
主節點 和 從節點 交互的命令,主要包括:
Sentinel
與 Sentinel
交互的命令,主要包括:
5.4 建議
-
儘可能在不同物理機上部署 Redis 哨兵所有節點
-
Redis 哨兵中的哨兵節點個數應該爲大於等於 3 且最好爲奇數
推薦奇數個節點,主要是從成本上考慮,因爲,集羣中,半數以上節點認爲主節點故障了,纔會選舉新的節點。這樣的話奇數個節點和偶數個節點允許宕機的節點數就是一樣的,比如 3 個節點和 4 個節點都只允許宕機一臺,那麼爲什麼要搞 4 個節點去浪費服務資源呢?但是 4 個節點的性能和容量肯定是更高的哈。
參考與來源
-
《Redis 開發與運維》
-
《Redis 核心技術與實戰》
-
https://redis.io/topics/sentinel
-
https://www.cnblogs.com/kismetv/p/9609938.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/706sq8BenxuxMK8QLUcGBw