1. 程式人生 > 其它 >redis_08 _ 哨兵叢集:哨兵掛了,主從庫還能切換嗎

redis_08 _ 哨兵叢集:哨兵掛了,主從庫還能切換嗎

上節課,我們學習了哨兵機制,它可以實現主從庫的自動切換。通過部署多個例項,就形成了一個哨兵叢集。哨兵叢集中的多個例項共同判斷,可以降低對主庫下線的誤判率。

但是,我們還是要考慮一個問題:如果有哨兵例項在執行時發生了故障,主從庫還能正常切換嗎?

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

如果你部署過哨兵叢集的話就會知道,在配置哨兵的資訊時,我們只需要用到下面的這個配置項,設定主庫的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的哨兵,要滿足兩個條件:第一,拿到半數以上的贊成票;第二,拿到的票數同時還需要大於等於哨兵配置檔案中的quorum值。以3個哨兵為例,假設此時的quorum設定為2,那麼,任何一個想成為Leader的哨兵只要拿到2張贊成票,就可以了。

這麼說你可能還不太好理解,我再畫一張圖片,展示一下3個哨兵、quorum為2的選舉過程。

在T1時刻,S1判斷主庫為“客觀下線”,它想成為Leader,就先給自己投一張贊成票,然後分別向S2和S3傳送命令,表示要成為Leader。

在T2時刻,S3判斷主庫為“客觀下線”,它也想成為Leader,所以也先給自己投一張贊成票,再分別向S1和S2傳送命令,表示要成為Leader。

在T3時刻,S1收到了S3的Leader投票請求。因為S1已經給自己投了一票Y,所以它不能再給其他哨兵投贊成票了,所以S1回覆N表示不同意。同時,S2收到了T2時S3傳送的Leader投票請求。因為S2之前沒有投過票,它會給第一個向它傳送投票請求的哨兵回覆Y,給後續再發送投票請求的哨兵回覆N,所以,在T3時,S2回覆S3,同意S3成為Leader。

在T4時刻,S2才收到T1時S1傳送的投票命令。因為S2已經在T3時同意了S3的投票請求,此時,S2給S1回覆N,表示不同意S1成為Leader。發生這種情況,是因為S3和S2之間的網路傳輸正常,而S1和S2之間的網路傳輸可能正好擁塞了,導致投票請求傳輸慢了。

最後,在T5時刻,S1得到的票數是來自它自己的一票Y和來自S2的一票N。而S3除了自己的贊成票Y以外,還收到了來自S2的一票Y。此時,S3不僅獲得了半數以上的Leader贊成票,也達到預設的quorum值(quorum為2),所以它最終成為了Leader。接著,S3會開始執行選主操作,而且在選定新主庫後,會給其他從庫和客戶端通知新主庫的資訊。

如果S3沒有拿到2票Y,那麼這輪投票就不會產生Leader。哨兵叢集會等待一段時間(也就是哨兵故障轉移超時時間的2倍),再重新選舉。這是因為,哨兵叢集能夠進行成功投票,很大程度上依賴於選舉命令的正常網路傳播。如果網路壓力較大或有短時堵塞,就可能導致沒有一個哨兵能拿到半數以上的贊成票。所以,等到網路擁塞好轉之後,再進行投票選舉,成功的概率就會增加。

需要注意的是,如果哨兵叢集只有2個例項,此時,一個哨兵要想成為Leader,必須獲得2票,而不是1票。所以,如果有個哨兵掛掉了,那麼,此時的叢集是無法進行主從庫切換的。因此,通常我們至少會配置3個哨兵例項。這一點很重要,你在實際應用時可不能忽略了。

小結

通常,我們在解決一個系統問題的時候,會引入一個新機制,或者設計一層新功能,就像我們在這兩節課學習的內容:為了實現主從切換,我們引入了哨兵;為了避免單個哨兵故障後無法進行主從切換,以及為了減少誤判率,又引入了哨兵叢集;哨兵叢集又需要有一些機制來支撐它的正常執行。

這節課上,我就向你介紹了支援哨兵叢集的這些關鍵機制,包括:

  • 基於pub/sub機制的哨兵叢集組成過程;
  • 基於INFO命令的從庫列表,這可以幫助哨兵和從庫建立連線;
  • 基於哨兵自身的pub/sub功能,這實現了客戶端和哨兵之間的事件通知。

對於主從切換,當然不是哪個哨兵想執行就可以執行的,否則就亂套了。所以,這就需要哨兵叢集在判斷了主庫“客觀下線”後,經過投票仲裁,選舉一個Leader出來,由它負責實際的主從切換,即由它來完成新主庫的選擇以及通知從庫與客戶端。

最後,我想再給你分享一個經驗:要保證所有哨兵例項的配置是一致的,尤其是主觀下線的判斷值down-after-milliseconds。我們曾經就踩過一個“坑”。當時,在我們的專案中,因為這個值在不同的哨兵例項上配置不一致,導致哨兵叢集一直沒有對有故障的主庫形成共識,也就沒有及時切換主庫,最終的結果就是叢集服務不穩定。所以,你一定不要忽略這條看似簡單的經驗。

每課一問

這節課上,我給你提一個小問題。

假設有一個Redis叢集,是“一主四從”,同時配置了包含5個哨兵例項的叢集,quorum值設為2。在執行過程中,如果有3個哨兵例項都發生故障了,此時,Redis主庫如果有故障,還能正確地判斷主庫“客觀下線”嗎?如果可以的話,還能進行主從庫自動切換嗎?此外,哨兵例項是不是越多越好呢,如果同時調大down-after-milliseconds值,對減少誤判是不是也有好處呢?

歡迎你在留言區跟我交流討論。如果你身邊也有要學習哨兵叢集相關知識點的朋友,也歡迎你能幫我把今天的內容分享給他們,幫助他們一起解決問題。我們下節課見。