第77篇 Redis中的Sentinel(哨兵模式)詳解

似梦亦非梦發表於2024-12-08

前言

Redis的高可用機制有持久化、複製、哨兵和叢集。其主要的作用和解決的問題分別是:

  • 持久化:持久化是最簡單的高可用方法(有時甚至不被歸為高可用的手段),主要作用是資料備份,即將資料儲存在硬碟,保證資料不會因程序退出而丟失。
  • 複製:複製是高可用Redis的基礎,哨兵和叢集都是在複製基礎上實現高可用的。複製主要實現了資料的多機備份,以及對於讀操作的負載均衡和簡單的故障恢復。缺陷:故障恢復無法自動化;寫操作無法負載均衡;儲存能力受到單機的限制。
  • 哨兵哨兵實現了主從複製中故障的自動化恢復。缺陷:寫操作無法負載均衡;儲存能力受到單機的限制。
  • 叢集:透過叢集,解決了寫操作無法負載均衡,以及儲存能力受到單機限制的問題,實現了較為完善的高可用方案。

為什麼要有哨兵

Redis的主從模式重點在於解決整體的承壓能力,利用從節點分擔讀取操作的壓力。但是其在容錯恢復等可靠性層面欠缺明顯,不具備自動的故障轉移與恢復能力

  • 如果slave從節點當機,整個redis依舊可以正常提供服務,待slave節點重新啟動後,可以恢復從master節點的資料同步、然後繼續提供服務。
  • 如果master主節點當機,則redis功能受損,無法繼續提供寫服務,直到手動修復master節點方可恢復。

當然,master節點故障後,也可以手動將其中一個從節點切換為新的master節點來恢復故障。而原先的master節點恢復後,需要手動將其降級為slave節點,對外提供只讀服務。

實際使用的時候,手動故障恢復的時效無法得到保證,為了支援自動的故障轉移與恢復能力,Redis在主從模式的基礎上進行最佳化增強,提供了哨兵(Sentinel)架構模式。

那麼就需要有一個機制,能夠監測主節點是否存活,如果發現主節點掛了,就選舉一個從節點切換為主節點,並且把新主節點的相關資訊通知給從節點和客戶端。

那麼就有以下三個問題需要解決:

  • 主庫真的掛了嗎?
  • 選擇哪個從庫作為主庫?
  • 如何把新主庫相關資訊通知給從庫和客戶端

1 哨兵機制流程

Redis的哨兵模式,就是在主從模式的基礎上,額外部署若干獨立的哨兵程序,透過哨兵程序去監視者Redis主從節點的狀態,一旦發現主節點當機,則哨兵可以重新從剩餘slave節點中推選一個新的節點並將其升級為master節點,以此保證整個系統功能可以正常使用。

哨兵負責三個任務:監控,選主(選擇主庫)和通知

  • 監控:監控是指哨兵程序執行時,週期性(預設1秒)給所有主從節點傳送 PING 命令,當主從節點收到 PING 命令後,會傳送一個響應命令給哨兵,這樣就可以檢測他們是否仍然線上執行。
    • 從庫沒有在規定時間內響應哨兵的PING命令,哨兵就會把它標記為"下線狀態";
    • 主庫沒有在規定時間呢響應哨兵的PING命令,哨兵就會判定主庫下線啟動選主流程。
  • 選主:哨兵在主庫掛了以後,按照一定規則從從庫中選出作為新的主庫。
  • 通知:哨兵將選出的新主庫連線資訊發給其他從庫,從庫和新主庫建立連線,執行replicaof命令,複製資料。同時,哨兵會把新主庫的連線資訊通知給客戶端,讓它們將操作請求傳送給新主庫上。

1.1 監控:如何判斷主庫是否真的掛了

哨兵程序會使用PING命令檢測主和從庫的連線情況,用來判斷例項狀態;
如果哨兵發現主庫或者從庫對PING命令響應超時,那麼哨兵就會把它標記為"主觀下線"。

  • 對於從庫,哨兵可以簡單標記為"主觀下線",因為從庫下線影響不大,叢集對外服務不會中斷。
  • 對於主庫,哨兵不能簡單標記為"主觀下線",開啟主從切換。因為可能存在一種情況:哨兵誤判,主庫沒有故障,可是一旦啟動選主和通知操作後續的選主和通知操作都會帶來額外的計算和通訊開銷。還可能產生腦裂。

確認主庫下線了,主庫才能被標記為客觀下線。需要注意的是客觀下線是主節點才有的概念;如果從節點和哨兵節點發生故障,被哨兵主觀下線後,不會再有後續的客觀下線和故障轉移操作。

什麼是誤判?
主庫實際沒有下線,但是哨兵以為它下線了。誤判產生原因:比如叢集網路壓力較大,出現網路擁塞,或者主庫本身壓力較大,導致主節點沒有在規定時間內響應哨兵的 PING 命令。

因此,這裡有兩個問題:

  • 需要儘量避免誤判 - 少數服從多數
  • 有兩個哨兵都判斷主庫是客觀下線了,那麼由哪個哨兵進行主從故障轉移

1.1.1 少數服從多數機制

哨兵叢集:哨兵以多例項組成的叢集模式進行部署(最少需要三臺機器來部署哨兵叢集)。透過多個哨兵節點一起判斷,就可以就可以避免單個哨兵因為自身網路狀況不好,而誤判主節點下線的情況。同時,多個哨兵網路同時不穩定的機率較小,讓它們一起決策讓誤判率降低。

當一個哨兵判斷主庫為主觀下線後就會向其它哨兵發起協商,其它哨兵就會根據自身與主庫的網路狀況,做出贊成或拒絕投票的響應。

而只有大多數哨兵例項判斷主庫都已經"主觀下線",主庫才會被標記"客觀下線",——— 即少數服從多數機制

image

當這個哨兵的贊同票數達到哨兵配置檔案中的 quorum 配置項設定的值後,這時主節點就會被該哨兵標記為客觀下線。

"客觀下線":N個哨兵例項,最好要有N/2+1個例項判斷主庫為"主觀下線",才能判斷為"客觀下線"。

1.1.2 由哪個哨兵進行主從故障轉移

為了減少誤判機率,所以哨兵是以哨兵叢集的方式存在的,以少數服從多數的機制來判斷主庫是否真的掛了。

那麼既然是叢集,由哨兵叢集中的哪個節點進行主從故障轉移呢?

所以這時候,還需要在哨兵叢集中選出一個 leader,讓 leader 來執行主從切換。

leader是從候選者中產生的,哪個哨兵節點判斷主節點為 客觀下線 ,這個哨兵節點就是leader的候選者。

假設有三個哨兵。當哨兵 B 先判斷到主節點主觀下線後,就會給其他例項傳送 is-master-down-by-addr 命令。接著,其他哨兵會根據自己和主節點的網路連線情況,做出贊成投票或者拒絕投票的響應。當哨兵 B 收到贊成票數達到哨兵配置檔案中的 quorum 配置項設定的值後,就會將主節點標記為 客觀下線,此時的哨兵 B 就是一個Leader 的候選者。

判斷主庫客觀下線後只是Leader 的候選者,還不是Leader
由於quorum 配置問題,有可能判斷主庫客觀下線的哨兵有多個。比如有3個哨兵,quorum為2,AB都認為主庫主觀下線,C認為主庫還線上,因此此時哨兵A收到主觀下線的值為2,另一個哨兵B收到的主觀下線值也為2,由於這兩個值都大於等於quorum,因此哨兵A和哨兵B都會將主庫標記為客觀下線。但不能讓哨兵A和哨兵B同時執行主從切換。因此需要從哨兵A和哨兵B中選出一個Leader來執行主從切換

此時Leader候選者會向其他哨兵傳送命令,表明希望成為 Leader 來執行主從切換,並讓所有其他哨兵對它進行投票。

每個哨兵只有一次投票機會,如果用完後就不能參與投票了,可以投給自己或投給別人,但是隻有Leader候選者才能把票投給自己。

那麼只要 Leader候選者 同時滿足以下兩個條件,就可以成為Leader:

  • 拿到半數以上的贊成票
  • 拿到的票數還要大於等於哨兵配置檔案中的 quorum 值。

也就是說,選舉的票數大於等於num(sentinel)/2+1時,Leader候選者 將成為 Leader,如果沒有超過繼續選舉

所以,quorum 的值建議設定為哨兵個數的二分之一加1,例如 3 個哨兵就設定 2,5 個哨兵設定為 3,而且哨兵節點的數量應該是奇數

哨兵判斷主節點客觀下線後,哨兵就要開始在從節點中選出一個從節點來做新主節點

這裡大家是不是和我一樣有個疑惑?如果三個哨兵同時判斷為主觀下線,那麼就有可能同時判斷為客觀下線,那麼就都是leader候選者了,那麼在投票時也都投給了自己,那麼leader不就永遠選不出來了嗎?
首先,哨兵對主從庫進行的線上狀態檢查的操作,是屬於一種時間事件,用一個定時器來完成,一般來說每100ms執行一次這些事件。實際上每個哨兵的定時器執行週期都會加上一個小小的隨機時間偏移,目的是讓每個哨兵執行上述操作的時間能稍微錯開些,也是為了避免它們都同時判定主庫主觀下線。
其次,實際上不同哨兵的網路情況、系統的壓力一般不完全一樣,接收到主觀下線協商訊息的時間也就可能不同,所以,它們同時做出主庫客觀下線判定的機率較小,一般也就有個先後關係。
最後,即使出現了都投給自己一票的情況,導致無法選出Leader,哨兵會停一段時間(一般是故障轉移超時時間failover_timeout的2倍),然後再可以進行下一輪投票。

1.2 選定新主庫

選定新主庫是 篩選打分的過程

image

1.2.1 篩選條件:

首先要把網路狀態不好的從節點給過濾掉。首先把已經下線的從節點過濾掉,然後把以往網路連線狀態不好的從節點也給過濾掉。

  • 檢查從庫的當前線上狀態,判斷他之前的網路連線狀態
  • 如果從庫總是和主庫斷連,斷連次數超過一定閾值,該從庫網路狀態不好。

判斷方式:
使用配置項down-after-milliseconds*10。down-after-milliseconds是主從節點斷連的最大連線超時時間。如果在 down-after-milliseconds 毫秒內,主從節點都沒有透過網路聯絡上,我們就可以認為主從節點斷連了。如果這個斷連次數超過10次,說明從庫網路狀態不好,不適合作為新主庫。

1.2.2 打分:

優先順序、複製進度、ID 號

  1. 第一輪:優先順序最高的從節點勝出
    透過配置slave-priority配置項,給不同從庫設定不同優先順序。比如兩個從庫記憶體大小不一樣,可以手動設定記憶體大的例項設定為一個高優先順序。選主時候哨兵會選出優先順序最高的打高分作為新主庫,如果得分一樣,那就開始第二輪打分。

  2. 第二輪:和舊主庫同步程度最接近的從庫得分高
    如果選擇與舊主庫同步最接近的從庫作為主庫,那麼新主庫上就有最新的資料。如果兩個從庫的slave_repl_offset值大小一樣,那麼就需要進入第三輪打分了

     如何判斷從庫和舊主庫間的同步進度?
     從庫的slave_repl_offset最接近舊主庫的master_repl_offset,那麼它的得分最高,可以作為新主庫。
    
  3. 第三輪:ID 號小的從節點勝出

每個從節點都有一個編號,這個編號就是 ID 號,是用來唯一標識從節點的。

image

1.3 通知

1.3.1 將選舉出的從節點解除從節點身份,升級為主節點

在選舉出從節點為新主節點後後,哨兵 leader 向被選中的從節點傳送 SLAVEOF no one 命令,讓這個從節點解除從節點的身份,將其變為新主節點。

image

1.3.2 將從節點指向新的主節點

哨兵 leader 下一步要做的就是,讓已下線主節點屬下的所有從節點指向新主節點,這一動作可以透過向從節點傳送 SLAVEOF 命令來實現。

image

1.3.3 通知客戶端主節點已經更換

這主要透過 Redis 的釋出者/訂閱者機制來實現的。每個哨兵節點提供釋出者/訂閱者機制,客戶端可以從哨兵訂閱訊息。

哨兵提供的訊息訂閱頻道有很多,不同頻道包含了主從節點切換過程中的不同關鍵事件,幾個常見的事件如下:

image

客戶端和哨兵建立連線後,客戶端會訂閱哨兵提供的頻道。主從切換完成後,哨兵就會向 +switch-master 頻道釋出新主節點的 IP 地址和埠的訊息,這個時候客戶端就可以收到這條資訊,然後用這裡面的新主節點的 IP 地址和埠進行通訊了

1.3.4 將原主節點變為從節點,指向新的主節點

繼續監視舊主節點,當舊主節點重新上線時,哨兵叢集就會向它傳送 SLAVEOF 命令,讓它成為新主節點的從節點

image

2.頻繁主從切換

由於redis是單執行緒執行的,如果有命令超過3秒,哨兵心跳檢查就會失敗,最終導致頻繁切換主從和腦裂情況

3.總結

本文首先介紹了哨兵的作用:監控、選主、通知;主要是實現主從節點故障轉移。哨兵叢集會監測主節點是否存活,如果發現主節點掛了,它就會選舉一個從節點切換為主節點,並且把新主節點的相關資訊通知給從節點和客戶端。

哨兵機制具體步驟如下:

  1. 第一輪投票:判斷主節點客觀下線
  2. 第二輪投票:選出哨兵leader,決定由哪個哨兵執行主從切換
  3. 由哨兵 leader 進行選主
  4. 由哨兵 leader 進行通知,實現主從故障轉移

在主從複製的基礎上,哨兵引入了主節點的自動故障轉移,進一步提高了Redis的高可用性;但是哨兵的缺陷同樣很明顯:哨兵無法對從節點進行自動故障轉移,在讀寫分離場景下,從節點故障會導致讀服務不可用,這就需要對從節點做額外的監控、切換操作。 此外,哨兵仍然沒有解決寫操作無法負載均衡、儲存能力受到單機限制的問題;這些問題的解決需要使用叢集。

出自:https://www.cnblogs.com/seven97-top/p/18583160

相關文章