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

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 個哨兵例項。這一點很重要,你在實際應用時可不能 忽略了。

小結

通常,我們在解決一個系統問題的時候,會引入一個新機制,或者設計一層新功能,就像 我們在這兩節課學習的內容:為了實現主從切換,我們引入了哨兵;為了避免單個哨兵故 障後無法進行主從切換,以及為了減少誤判率,又引入了哨兵叢集;哨兵叢集又需要有一 些機制來支撐它的正常執行。 這節課上,我就向你介紹了支援哨兵叢集的這些關鍵機制,包括: 對於主從切換,當然不是哪個哨兵想執行就可以執行的,否則就亂套了。所以,這就需要 哨兵叢集在判斷了主庫“客觀下線”後,經過投票仲裁,選舉一個 Leader 出來,由它負責 實際的主從切換,即由它來完成新主庫的選擇以及通知從庫與客戶端。 最後,我想再給你分享一個經驗:要保證所有哨兵例項的配置是一致的,尤其是主觀下線 的判斷值 down-after-milliseconds。我們曾經就踩過一個“坑”。當時,在我們的專案 中,因為這個值在不同的哨兵例項上配置不一致,導致哨兵叢集一直沒有對有故障的主庫 形成共識,也就沒有及時切換主庫,最終的結果就是叢集服務不穩定。所以,你一定不要 忽略這條看似簡單的經驗。

每課一問

這節課上,我給你提一個小問題。 假設有一個 Redis 叢集,是“一主四從”,同時配置了包含 5 個哨兵例項的叢集, quorum 值設為 2。在執行過程中,如果有 3 個哨兵例項都發生故障了,此時,Redis 主 庫如果有故障,還能正確地判斷主庫“客觀下線”嗎?如果可以的話,還能進行主從庫自 動切換嗎?此外,哨兵例項是不是越多越好呢,如果同時調大 down-after-milliseconds 值,對減少誤判是不是也有好處呢?   因為判定主庫“客觀下線”的依據是,認為主庫“主觀下線”的哨兵個數要大於等於 quorum 值,現在還剩 2 個哨兵例項,個數正好等於 quorum 值,所以還能正常判斷主庫 是否處於“客觀下線”狀態。如果一個哨兵想要執行主從切換,就要獲到半數以上的哨兵 投票贊成,也就是至少需要 3 個哨兵投票贊成。但是,現在只有 2 個哨兵了,所以就無法 進行主從切換了。   哨兵例項越多,誤判率會越低,但是在判定主庫下線和選舉 Leader 時,例項需要拿到的贊 成票數也越多,等待所有哨兵投完票的時間可能也會相應增加,主從庫切換的時間也會變 長,客戶端容易堆積較多的請求操作,可能會導致客戶端請求溢位,從而造成請求丟失。 如果業務層對 Redis 的操作有響應時間要求,就可能會因為新主庫一直沒有選定,新操作 無法執行而發生超時報警。 調大 down-after-milliseconds 後,可能會導致這樣的情況:主庫實際已經發生故障了, 但是哨兵過了很長時間才判斷出來,這就會影響到 Redis 對業務的可用性。