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