Redis哨兵集群:哨兵掛了,主從庫還能切換嗎?
通過部署多個哨兵實例,我們構建了一個哨兵集群,這個集群中的多個實例共同協作,以降低對主庫下線的誤判率。
然而,還有一個重要問題需要考慮:如果哨兵集群中的某個實例發生故障,主從庫是否能夠繼續正常切換呢?
實際上,一旦多個實例組成了哨兵集群,即使有個別哨兵實例出現故障而無法正常運行,其他健康的哨兵實例仍然能夠繼續協同工作,完成主從庫切換的各項任務,包括判斷主庫的下線狀態、選擇新的主庫,以及通知從庫和客戶端。
如果你曾經部署過哨兵集群,你會發現,在配置哨兵信息時,我們只需要指定主庫的 IP 和端口,而無需明確配置其他哨兵實例的連接信息。這是因為哨兵集群中的各個實例會相互感知和發現,形成一種自動協作的機制。
sentinel monitor <master-name> <ip> <redis-port> <quorum>
這些哨兵實例既然都不知道彼此的地址,又是怎么組成集群的呢?要弄明白這個問題,我們就需要學習一下哨兵集群的組成和運行機制了。
基于 pub/sub 機制的哨兵集群組成
哨兵實例之間可以相互發現,要歸功于 Redis 提供的 pub/sub 機制,也就是發布 / 訂閱機制。
哨兵只要和主庫建立起了連接,就可以在主庫上發布消息了,比如說發布它自己的連接信息(IP 和端口)。同時,它也可以從主庫上訂閱消息,獲得其他哨兵發布的連接信息。當多個哨兵實例都在主庫上做了發布和訂閱操作后,它們之間就能知道彼此的 IP 地址和端口。
除了哨兵實例,我們自己編寫的應用程序也可以通過 Redis 進行消息的發布和訂閱。所以,為了區分不同應用的消息,Redis 會以頻道的形式,對這些消息進行分門別類的管理。所謂的頻道,實際上就是消息的類別。當消息類別相同時,它們就屬于同一個頻道。反之,就屬于不同的頻道。只有訂閱了同一個頻道的應用,才能通過發布的消息進行信息交換。
在主從集群中,主庫上有一個名為“__sentinel__:hello”的頻道,不同哨兵就是通過它來相互發現,實現互相通信的。
我來舉個例子,具體說明一下。在下圖中,哨兵 1 把自己的 IP(172.16.19.3)和端口(26579)發布到“__sentinel__:hello”頻道上,哨兵 2 和 3 訂閱了該頻道。那么此時,哨兵 2 和 3 就可以從這個頻道直接獲取哨兵 1 的 IP 地址和端口號。
然后,哨兵 2、3 可以和哨兵 1 建立網絡連接。通過這個方式,哨兵 2 和 3 也可以建立網絡連接,這樣一來,哨兵集群就形成了。它們相互間可以通過網絡連接進行通信,比如說對主庫有沒有下線這件事兒進行判斷和協商。
圖片
哨兵除了彼此之間建立起連接形成集群外,還需要和從庫建立連接。這是因為,在哨兵的監控任務中,它需要對主從庫都進行心跳判斷,而且在主從庫切換完成后,它還需要通知從庫,讓它們和新主庫進行同步。
那么,哨兵是如何知道從庫的 IP 地址和端口的呢?
這是由哨兵向主庫發送 INFO 命令來完成的。就像下圖所示,哨兵 2 給主庫發送 INFO 命令,主庫接受到這個命令后,就會把從庫列表返回給哨兵。接著,哨兵就可以根據從庫列表中的連接信息,和每個從庫建立連接,并在這個連接上持續地對從庫進行監控。哨兵 1 和 3 可以通過相同的方法和從庫建立連接。
圖片
通過 pub/sub 機制,哨兵之間可以形成一個協作集群。此外,哨兵還能通過 INFO 命令獲得從庫的連接信息,建立連接并進行監控。
然而,哨兵的連接工作不僅僅限于主庫和從庫。當主從庫發生切換后,客戶端需要獲取新主庫的連接信息,以便繼續發送請求操作。因此,哨兵的任務還包括將新主庫的信息傳達給客戶端。
在實際使用哨兵時,有時候我們需要解決這樣的問題:如何讓客戶端能夠通過監控來了解哨兵進行主從切換的進度?具體來說,客戶端需要了解主從切換進行到哪個步驟了。這就意味著客戶端需要獲取有關哨兵集群在監控、選主和切換等過程中發生的各種事件的信息。
在這種情況下,我們仍然可以借助 pub/sub 機制,以實現哨兵和客戶端之間的信息同步。這種機制可以幫助客戶端跟蹤主從切換的各個步驟。
基于 pub/sub 機制的客戶端事件通知
從根本上說,哨兵就是一個以特定模式運行的 Redis 實例。然而,它不會處理請求操作,而是專注于監控、選主和通知的任務。每個哨兵實例也提供 pub/sub 機制,允許客戶端通過訂閱消息來獲取信息。這些消息訂閱頻道有眾多,各自包含了主從庫切換過程中的不同重要事件。
考慮到頻道眾多,嘗試同時理解它們可能會讓人感到不知所措。為了幫助你更輕松地理解,我將匯總一些關鍵頻道,這些頻道涵蓋了幾個重要事件,包括主庫下線判斷、新主庫的選定和從庫的重新配置。
圖片
知道了這些頻道之后,你就可以讓客戶端從哨兵這里訂閱消息了。具體的操作步驟是,客戶端讀取哨兵的配置文件后,可以獲得哨兵的地址和端口,和哨兵建立網絡連接。然后,我們可以在客戶端執行訂閱命令,來獲取不同的事件消息。
舉個例子,你可以執行如下命令,來訂閱“所有實例進入客觀下線狀態的事件”:
SUBSCRIBE +odown
當然,你也可以執行如下命令,訂閱所有的事件:
PSUBSCRIBE *
當哨兵把新主庫選擇出來后,客戶端就會看到下面的 switch-master 事件。這個事件表示主庫已經切換了,新主庫的 IP 地址和端口信息已經有了。這個時候,客戶端就可以用這里面的新主庫地址和端口進行通信了
switch-master <master name> <oldip> <oldport> <newip> <newport>
有了這些事件通知機制,客戶端不僅可以獲取新主庫的連接信息,還可以跟蹤主從庫切換過程中的各個重要事件。這為客戶端提供了切換的實時狀態,使其了解切換的進展情況。
現在,借助 pub/sub 機制,哨兵與哨兵、哨兵與從庫、哨兵與客戶端之間都建立了緊密的聯系。再加上之前我們介紹的主庫下線判定和新主庫選定的依據,哨兵集群的監控、選主和通知三大任務基本已經可以正常運行了。不過,我們仍需考慮一個問題:在主庫發生故障后,哨兵集群中有多個實例,那么該由哪個哨兵來執行實際的主從切換呢?
由哪個哨兵執行主從切換?
確定由哪個哨兵來執行主從切換的過程,類似于主庫“客觀下線”的判斷過程,它也是一個“投票仲裁”的過程。在詳細探討這個過程之前,我們先回顧一下判斷主庫“客觀下線”的仲裁過程。
在哨兵集群中,要斷定主庫“客觀下線”需要多個實例達成一致意見。我在之前的課程中分享了判斷“客觀下線”的原則,接下來,我們將深入探討這個具體的判斷過程。
一旦一個哨兵實例認為主庫“主觀下線”,它會向其他哨兵實例發送 is-master-down-by-addr 命令。其他實例根據它們自身與主庫的連接情況,給出 Y 或 N 的響應。在這里,Y 代表贊成,N 代表反對。
圖片
在獲得足夠的贊成票后,一個哨兵就可以將主庫標記為“客觀下線”。所需的贊成票數是通過哨兵配置文件中的 quorum 配置項來設置的。舉個例子,如果現有 5 個哨兵,quorum 配置為 3,那么一個哨兵需要獲得 3 張贊成票才能將主庫標記為“客觀下線”。這里的 3 張贊成票包括該哨兵自己的一張票和其他兩個哨兵的贊成票。
當滿足了所需的贊成票數后,該哨兵會向其他哨兵發送請求,表明自己希望執行主從切換,并請求進行投票,這個投票過程被稱為“Leader選舉”。因為最終執行主從切換的哨兵被稱為Leader,而投票過程則決定了誰將成為這個Leader。
在Leader選舉過程中,任何希望成為Leader的哨兵都必須滿足兩個條件:首先,必須獲得半數以上的贊成票;其次,獲得的票數還必須大于或等于哨兵配置文件中的quorum值。以擁有3個哨兵為例,如果quorum設置為2,那么任何想成為Leader的哨兵只需要獲得2張以上的贊成票即可。
這么說你可能還不太好理解,我再畫一張圖片,展示一下 3 個哨兵、quorum 為 2 的選舉過程。
圖片
在T1時刻,S1判斷主庫已經“客觀下線”,并試圖成為Leader。首先,S1給自己投了一張贊成票,接著,S1發送請求命令給S2和S3,表明它想成為Leader。
在T2時刻,S3也判斷主庫已經“客觀下線”并希望成為Leader。同樣地,S3首先給自己投了一張贊成票,之后向S1和S2發送請求命令,表明它想成為Leader。
在T3時刻,S1收到了S3發來的Leader投票請求。因為S1已經投了一票贊成自己,所以無法再為其他哨兵投贊成票,于是S1回復N,表示不同意。同時,S2在T3時刻接到S3的Leader投票請求,由于S2之前沒有進行投票,它會為首個向它發送投票請求的哨兵回復Y,而后續發送請求的哨兵則會收到N。因此,在T3時刻,S2回復S3,同意S3成為Leader。
在T4時刻,S2最終收到了T1時刻S1發來的投票請求。由于S2在T3時刻已經同意S3成為Leader,因此在T4時刻,S2回復S1,不同意S1成為Leader。此情況出現的原因可能是S3和S2之間的網絡通信正常,而S1和S2之間的網絡通信出現了阻塞,導致投票請求傳輸緩慢。
最后,在T5時刻,S1收到的票數包括一張贊成票Y(來自自己)和一張反對票N(來自S2)。而S3除了自己的一張贊成票Y,還收到了S2的一張贊成票Y。在這一時刻,S3不僅獲得了半數以上的Leader贊成票,還達到了預設的quorum值(quorum為2),因此,S3最終成為Leader。接下來,S3將開始執行選主操作,并在選定新的主庫后,通知其他從庫和客戶端新主庫的信息。
如果S3未獲得2張贊成票,那么這輪投票將不會產生Leader。哨兵集群將等待一段時間(即哨兵故障轉移超時時間的2倍),然后重新進行選舉。這是因為哨兵集群的成功投票在很大程度上取決于選舉命令的正常網絡傳播。如果網絡負載較大或短時阻塞發生,可能導致沒有哨兵能夠獲得半數以上的贊成票。因此,在網絡壓力減輕后重新進行選舉,將增加成功的機會。
需要注意的是,如果哨兵集群只有2個實例,那么一個哨兵要想成為Leader,必須獲得2張贊成票而不是1張。因此,如果有一個哨兵出現故障,那么集群將無法執行主從庫切換。因此,通常情況下,我們會配置至少3個哨兵實例,這一點非常重要,務必在實際應用中予以注意。
小結
一般情況下,當我們解決系統問題時,我們會引入新的機制或者設計新的功能層,就像我們在之前學習的內容一樣:為了實現主從切換,我們引入了哨兵機制;為了應對單個哨兵故障導致無法進行主從切換的情況,以及為了降低誤判率,我們引入了哨兵集群;而哨兵集群也需要一些機制來支持其正常運行。
本篇介紹了支持哨兵集群的關鍵機制,包括:
- 基于發布/訂閱(pub/sub)機制的哨兵集群組成過程;
- 基于INFO命令的從庫列表,用于幫助哨兵與從庫建立連接;
- 基于哨兵自身的發布/訂閱功能,實現了客戶端和哨兵之間的事件通知。
在進行主從切換時,當然不是任何哨兵都可以隨意執行的,否則會造成混亂。因此,哨兵集群需要經過投票仲裁來選舉出一個領導者,由它負責實際的主從切換,即負責選擇新的主庫并通知從庫和客戶端。
最后,我想分享一個經驗:要確保所有哨兵實例的配置保持一致,特別是主觀下線的判斷值 down-after-milliseconds。我們曾經因為這個值在不同的哨兵實例上配置不一致而遇到問題。這導致哨兵集群無法達成對有故障的主庫的共識,最終導致集群服務不穩定。因此,請務必注意這條看似簡單的經驗。