Redis 的主庫(kù)掛了,如何不間斷服務(wù)?
我們了解到在主從庫(kù)集群模式下,如果從庫(kù)發(fā)生故障,客戶端可以繼續(xù)向主庫(kù)或其他從庫(kù)發(fā)送請(qǐng)求,執(zhí)行相應(yīng)的操作。然而,當(dāng)主庫(kù)發(fā)生故障時(shí),會(huì)直接影響從庫(kù)的同步,因?yàn)榇藭r(shí)從庫(kù)失去了可用的主庫(kù)進(jìn)行數(shù)據(jù)復(fù)制。
而且,如果客戶端發(fā)送的都是讀操作請(qǐng)求,那還可以由從庫(kù)繼續(xù)提供服務(wù),這在純讀的業(yè)務(wù)場(chǎng)景下還能被接受。但是,一旦有寫操作請(qǐng)求了,按照主從庫(kù)模式下的讀寫分離要求,需要由主庫(kù)來(lái)完成寫操作。此時(shí),也沒(méi)有實(shí)例可以來(lái)服務(wù)客戶端的寫操作請(qǐng)求了,如下圖所示:
圖片
主庫(kù)故障后,導(dǎo)致從庫(kù)無(wú)法提供寫操作的服務(wù),這種情況是不可接受的。因此,在主庫(kù)發(fā)生故障時(shí),我們需要啟動(dòng)一個(gè)新的主庫(kù),通常是將一個(gè)從庫(kù)升級(jí)為主庫(kù)并將其作為新的主庫(kù)。然而,這涉及到解決三個(gè)核心問(wèn)題:
- 如何確定主庫(kù)已經(jīng)宕機(jī)了?
- 從眾多從庫(kù)中選擇哪一個(gè)作為新的主庫(kù)?
- 如何通知從庫(kù)和客戶端關(guān)于新主庫(kù)的變化?
這正是哨兵機(jī)制的任務(wù)。在 Redis 主從集群中,哨兵機(jī)制是實(shí)現(xiàn)自動(dòng)主從切換的關(guān)鍵,它成功地解決了上述三個(gè)問(wèn)題,確保系統(tǒng)的可用性。接下來(lái),我們將深入學(xué)習(xí)和了解哨兵機(jī)制的工作原理。
哨兵機(jī)制的基本流程
哨兵其實(shí)就是一個(gè)運(yùn)行在特殊模式下的 Redis 進(jìn)程,主從庫(kù)實(shí)例運(yùn)行的同時(shí),它也在運(yùn)行。哨兵主要負(fù)責(zé)的就是三個(gè)任務(wù):監(jiān)控、選主(選擇主庫(kù))和通知。
我們先看監(jiān)控。監(jiān)控是指哨兵進(jìn)程在運(yùn)行時(shí),周期性地給所有的主從庫(kù)發(fā)送 PING 命令,檢測(cè)它們是否仍然在線運(yùn)行。如果從庫(kù)沒(méi)有在規(guī)定時(shí)間內(nèi)響應(yīng)哨兵的 PING 命令,哨兵就會(huì)把它標(biāo)記為“下線狀態(tài)”;同樣,如果主庫(kù)也沒(méi)有在規(guī)定時(shí)間內(nèi)響應(yīng)哨兵的 PING 命令,哨兵就會(huì)判定主庫(kù)下線,然后開(kāi)始自動(dòng)切換主庫(kù)的流程。
這個(gè)流程首先是執(zhí)行哨兵的第二個(gè)任務(wù),選主。主庫(kù)掛了以后,哨兵就需要從很多個(gè)從庫(kù)里,按照一定的規(guī)則選擇一個(gè)從庫(kù)實(shí)例,把它作為新的主庫(kù)。這一步完成后,現(xiàn)在的集群里就有了新主庫(kù)。
然后,哨兵會(huì)執(zhí)行最后一個(gè)任務(wù):通知。在執(zhí)行通知任務(wù)時(shí),哨兵會(huì)把新主庫(kù)的連接信息發(fā)給其他從庫(kù),讓它們執(zhí)行 replicaof 命令,和新主庫(kù)建立連接,并進(jìn)行數(shù)據(jù)復(fù)制。同時(shí),哨兵會(huì)把新主庫(kù)的連接信息通知給客戶端,讓它們把請(qǐng)求操作發(fā)到新主庫(kù)上。
我畫了一張圖片,展示了這三個(gè)任務(wù)以及它們各自的目標(biāo)。
哨兵機(jī)制的三項(xiàng)任務(wù)與目標(biāo)
在這三個(gè)任務(wù)中,通知任務(wù)相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,哨兵只需要把新主庫(kù)信息發(fā)給從庫(kù)和客戶端,讓它們和新主庫(kù)建立連接就行,并不涉及決策的邏輯。但是,在監(jiān)控和選主這兩個(gè)任務(wù)中,哨兵需要做出兩個(gè)決策:
在監(jiān)控任務(wù)中,哨兵需要判斷主庫(kù)是否處于下線狀態(tài);
在選主任務(wù)中,哨兵也要決定選擇哪個(gè)從庫(kù)實(shí)例作為主庫(kù)。
接下來(lái),我們就先說(shuō)說(shuō)如何判斷主庫(kù)的下線狀態(tài)。
你首先要知道的是,哨兵對(duì)主庫(kù)的下線判斷有“主觀下線”和“客觀下線”兩種。那么,為什么會(huì)存在兩種判斷呢?它們的區(qū)別和聯(lián)系是什么呢?
主觀下線和客觀下線
我先解釋下什么是“主觀下線”。
哨兵進(jìn)程會(huì)使用 PING 命令檢測(cè)它自己和主、從庫(kù)的網(wǎng)絡(luò)連接情況,用來(lái)判斷實(shí)例的狀態(tài)。如果哨兵發(fā)現(xiàn)主庫(kù)或從庫(kù)對(duì) PING 命令的響應(yīng)超時(shí)了,那么,哨兵就會(huì)先把它標(biāo)記為“主觀下線”。
如果檢測(cè)的是從庫(kù),那么,哨兵簡(jiǎn)單地把它標(biāo)記為“主觀下線”就行了,因?yàn)閺膸?kù)的下線影響一般不太大,集群的對(duì)外服務(wù)不會(huì)間斷。
但是,如果檢測(cè)的是主庫(kù),那么,哨兵還不能簡(jiǎn)單地把它標(biāo)記為“主觀下線”,開(kāi)啟主從切換。因?yàn)楹苡锌赡艽嬖谶@么一個(gè)情況:那就是哨兵誤判了,其實(shí)主庫(kù)并沒(méi)有故障??墒?,一旦啟動(dòng)了主從切換,后續(xù)的選主和通知操作都會(huì)帶來(lái)額外的計(jì)算和通信開(kāi)銷。
為了避免這些不必要的開(kāi)銷,我們要特別注意誤判的情況。
首先,我們要知道啥叫誤判。很簡(jiǎn)單,就是主庫(kù)實(shí)際并沒(méi)有下線,但是哨兵誤以為它下線了。誤判一般會(huì)發(fā)生在集群網(wǎng)絡(luò)壓力較大、網(wǎng)絡(luò)擁塞,或者是主庫(kù)本身壓力較大的情況下。
一旦哨兵判斷主庫(kù)下線了,就會(huì)開(kāi)始選擇新主庫(kù),并讓從庫(kù)和新主庫(kù)進(jìn)行數(shù)據(jù)同步,這個(gè)過(guò)程本身就會(huì)有開(kāi)銷,例如,哨兵要花時(shí)間選出新主庫(kù),從庫(kù)也需要花時(shí)間和新主庫(kù)同步。而在誤判的情況下,主庫(kù)本身根本就不需要進(jìn)行切換的,所以這個(gè)過(guò)程的開(kāi)銷是沒(méi)有價(jià)值的。正因?yàn)檫@樣,我們需要判斷是否有誤判,以及減少誤判。
那怎么減少誤判呢?在日常生活中,當(dāng)我們要對(duì)一些重要的事情做判斷的時(shí)候,經(jīng)常會(huì)和家人或朋友一起商量一下,然后再做決定。
哨兵機(jī)制也是類似的,它通常會(huì)采用多實(shí)例組成的集群模式進(jìn)行部署,這也被稱為哨兵集群。引入多個(gè)哨兵實(shí)例一起來(lái)判斷,就可以避免單個(gè)哨兵因?yàn)樽陨砭W(wǎng)絡(luò)狀況不好,而誤判主庫(kù)下線的情況。同時(shí),多個(gè)哨兵的網(wǎng)絡(luò)同時(shí)不穩(wěn)定的概率較小,由它們一起做決策,誤判率也能降低。
這節(jié)課,你只需要先理解哨兵集群在減少誤判方面的作用,就行了。至于具體的運(yùn)行機(jī)制,下節(jié)課我們?cè)僦攸c(diǎn)學(xué)習(xí)。
在判斷主庫(kù)是否下線時(shí),不能由一個(gè)哨兵說(shuō)了算,只有大多數(shù)的哨兵實(shí)例,都判斷主庫(kù)已經(jīng)“主觀下線”了,主庫(kù)才會(huì)被標(biāo)記為“客觀下線”,這個(gè)叫法也是表明主庫(kù)下線成為一個(gè)客觀事實(shí)了。這個(gè)判斷原則就是:少數(shù)服從多數(shù)。同時(shí),這會(huì)進(jìn)一步觸發(fā)哨兵開(kāi)始主從切換流程。
為了方便你理解,我再畫一張圖展示一下這里的邏輯。
如下圖所示,Redis 主從集群有一個(gè)主庫(kù)、三個(gè)從庫(kù),還有三個(gè)哨兵實(shí)例。在圖片的左邊,哨兵 2 判斷主庫(kù)為“主觀下線”,但哨兵 1 和 3 卻判定主庫(kù)是上線狀態(tài),此時(shí),主庫(kù)仍然被判斷為處于上線狀態(tài)。在圖片的右邊,哨兵 1 和 2 都判斷主庫(kù)為“主觀下線”,此時(shí),即使哨兵 3 仍然判斷主庫(kù)為上線狀態(tài),主庫(kù)也被標(biāo)記為“客觀下線”了。
客觀下線的判斷
簡(jiǎn)單來(lái)說(shuō),“客觀下線”的標(biāo)準(zhǔn)就是,當(dāng)有 N 個(gè)哨兵實(shí)例時(shí),最好要有 N/2 + 1 個(gè)實(shí)例判斷主庫(kù)為“主觀下線”,才能最終判定主庫(kù)為“客觀下線”。這樣一來(lái),就可以減少誤判的概率,也能避免誤判帶來(lái)的無(wú)謂的主從庫(kù)切換。(當(dāng)然,有多少個(gè)實(shí)例做出“主觀下線”的判斷才可以,可以由 Redis 管理員自行設(shè)定)。
好了,到這里,你可以看到,借助于多個(gè)哨兵實(shí)例的共同判斷機(jī)制,我們就可以更準(zhǔn)確地判斷出主庫(kù)是否處于下線狀態(tài)。如果主庫(kù)的確下線了,哨兵就要開(kāi)始下一個(gè)決策過(guò)程了,即從許多從庫(kù)中,選出一個(gè)從庫(kù)來(lái)做新主庫(kù)。
如何選定新主庫(kù)?
一般來(lái)說(shuō),我把哨兵選擇新主庫(kù)的過(guò)程稱為“篩選 + 打分”。簡(jiǎn)單來(lái)說(shuō),我們?cè)诙鄠€(gè)從庫(kù)中,先按照一定的篩選條件,把不符合條件的從庫(kù)去掉。然后,我們?cè)侔凑?/span>一定的規(guī)則,給剩下的從庫(kù)逐個(gè)打分,將得分最高的從庫(kù)選為新主庫(kù),如下圖所示:
新主庫(kù)的選擇過(guò)程
在上述段落中,我們需要明晰兩個(gè)關(guān)鍵的“一定”。現(xiàn)在,讓我們?cè)敿?xì)討論這里的“一定”究竟指的是什么。
首先,讓我們來(lái)探討所選從庫(kù)的篩選條件。
通常情況下,我們必須確保所選的從庫(kù)仍然處于在線運(yùn)行狀態(tài)。然而,在選擇新主庫(kù)時(shí),僅僅考慮從庫(kù)的當(dāng)前在線狀態(tài)是不夠的,因?yàn)檎T诰€并不代表它就是最佳的主庫(kù)選擇。
設(shè)想一下,如果在選主時(shí),我們選中一個(gè)正常在線的從庫(kù)并開(kāi)始使用它。不過(guò),不久后,它的網(wǎng)絡(luò)連接發(fā)生故障,這將迫使我們重新選擇主庫(kù)。這顯然不符合我們的期望。
因此,在進(jìn)行主庫(kù)選擇時(shí),除了檢查從庫(kù)的當(dāng)前在線狀態(tài),還需要考慮它以前的網(wǎng)絡(luò)連接狀態(tài)。如果一個(gè)從庫(kù)經(jīng)常與主庫(kù)斷開(kāi)連接,并且斷開(kāi)連接的次數(shù)超出了特定的閾值,那么我們就有理由相信,這個(gè)從庫(kù)的網(wǎng)絡(luò)狀況并不太可靠,因此可以將其排除在主庫(kù)的選擇之外。
具體怎么判斷呢?你使用配置項(xiàng) down-after-milliseconds * 10。其中,down-after-milliseconds 是我們認(rèn)定主從庫(kù)斷連的最大連接超時(shí)時(shí)間。如果在 down-after-milliseconds 毫秒內(nèi),主從節(jié)點(diǎn)都沒(méi)有通過(guò)網(wǎng)絡(luò)聯(lián)系上,我們就可以認(rèn)為主從節(jié)點(diǎn)斷連了。如果發(fā)生斷連的次數(shù)超過(guò)了 10 次,就說(shuō)明這個(gè)從庫(kù)的網(wǎng)絡(luò)狀況不好,不適合作為新主庫(kù)。
好了,這樣我們就過(guò)濾掉了不適合做主庫(kù)的從庫(kù),完成了篩選工作。
接下來(lái)就要給剩余的從庫(kù)打分了。我們可以分別按照三個(gè)規(guī)則依次進(jìn)行三輪打分,這三個(gè)規(guī)則分別是從庫(kù)優(yōu)先級(jí)、從庫(kù)復(fù)制進(jìn)度以及從庫(kù) ID 號(hào)。只要在某一輪中,有從庫(kù)得分最高,那么它就是主庫(kù)了,選主過(guò)程到此結(jié)束。如果沒(méi)有出現(xiàn)得分最高的從庫(kù),那么就繼續(xù)進(jìn)行下一輪。
第一輪:優(yōu)先級(jí)最高的從庫(kù)得分高。
用戶可以通過(guò) slave-priority 配置項(xiàng),給不同的從庫(kù)設(shè)置不同優(yōu)先級(jí)。比如,你有兩個(gè)從庫(kù),它們的內(nèi)存大小不一樣,你可以手動(dòng)給內(nèi)存大的實(shí)例設(shè)置一個(gè)高優(yōu)先級(jí)。在選主時(shí),哨兵會(huì)給優(yōu)先級(jí)高的從庫(kù)打高分,如果有一個(gè)從庫(kù)優(yōu)先級(jí)最高,那么它就是新主庫(kù)了。如果從庫(kù)的優(yōu)先級(jí)都一樣,那么哨兵開(kāi)始第二輪打分。
第二輪:和舊主庫(kù)同步程度最接近的從庫(kù)得分高。
這個(gè)規(guī)則的依據(jù)是,如果選擇和舊主庫(kù)同步最接近的那個(gè)從庫(kù)作為主庫(kù),那么,這個(gè)新主庫(kù)上就有最新的數(shù)據(jù)。
如何判斷從庫(kù)和舊主庫(kù)間的同步進(jìn)度呢?
上節(jié)課我向你介紹過(guò),主從庫(kù)同步時(shí)有個(gè)命令傳播的過(guò)程。在這個(gè)過(guò)程中,主庫(kù)會(huì)用 master_repl_offset 記錄當(dāng)前的最新寫操作在 repl_backlog_buffer 中的位置,而從庫(kù)會(huì)用 slave_repl_offset 這個(gè)值記錄當(dāng)前的復(fù)制進(jìn)度。
此時(shí),我們想要找的從庫(kù),它的 slave_repl_offset 需要最接近 master_repl_offset。如果在所有從庫(kù)中,有從庫(kù)的 slave_repl_offset 最接近 master_repl_offset,那么它的得分就最高,可以作為新主庫(kù)。
就像下圖所示,舊主庫(kù)的 master_repl_offset 是 1000,從庫(kù) 1、2 和 3 的 slave_repl_offset 分別是 950、990 和 900,那么,從庫(kù) 2 就應(yīng)該被選為新主庫(kù)。
基于復(fù)制進(jìn)度的新主庫(kù)選主原則
當(dāng)然,如果有兩個(gè)從庫(kù)的 slave_repl_offset 值大小是一樣的(例如,從庫(kù) 1 和從庫(kù) 2 的 slave_repl_offset 值都是 990),我們就需要給它們進(jìn)行第三輪打分了。
第三輪:ID 號(hào)小的從庫(kù)得分高。
每個(gè)實(shí)例都會(huì)有一個(gè) ID,這個(gè) ID 就類似于這里的從庫(kù)的編號(hào)。目前,Redis 在選主庫(kù)時(shí),有一個(gè)默認(rèn)的規(guī)定:在優(yōu)先級(jí)和復(fù)制進(jìn)度都相同的情況下,ID 號(hào)最小的從庫(kù)得分最高,會(huì)被選為新主庫(kù)。
到這里,新主庫(kù)就被選出來(lái)了,“選主”這個(gè)過(guò)程就完成了。
我們?cè)倩仡櫹逻@個(gè)流程。首先,哨兵會(huì)按照在線狀態(tài)、網(wǎng)絡(luò)狀態(tài),篩選過(guò)濾掉一部分不符合要求的從庫(kù),然后,依次按照優(yōu)先級(jí)、復(fù)制進(jìn)度、ID 號(hào)大小再對(duì)剩余的從庫(kù)進(jìn)行打分,只要有得分最高的從庫(kù)出現(xiàn),就把它選為新主庫(kù)。
小結(jié)
我們已經(jīng)一起探討了哨兵機(jī)制,這是確保 Redis 提供持續(xù)服務(wù)的關(guān)鍵要素。具體來(lái)說(shuō),主從數(shù)據(jù)庫(kù)的數(shù)據(jù)同步是數(shù)據(jù)可靠性的基石。在主數(shù)據(jù)庫(kù)發(fā)生故障時(shí),自動(dòng)執(zhí)行的主從切換是服務(wù)不中斷的重要支持。
Redis 的哨兵機(jī)制自動(dòng)執(zhí)行以下三項(xiàng)重要功能,實(shí)現(xiàn)了主從切換,從而降低了 Redis 集群的維護(hù)成本:
- 監(jiān)測(cè)主庫(kù)運(yùn)行狀態(tài):監(jiān)測(cè)主庫(kù)的運(yùn)行狀態(tài),以確定主庫(kù)是否客觀下線,即無(wú)法提供有效服務(wù)。
- 選擇新主庫(kù):一旦主庫(kù)被客觀下線,哨兵機(jī)制會(huì)選擇一個(gè)新的主庫(kù),以維護(hù)集群的可用性。
- 通知從庫(kù)和客戶端:選定新主庫(kù)后,哨兵會(huì)通知相關(guān)從庫(kù)切換到新的主庫(kù),以確保數(shù)據(jù)同步。同時(shí),客戶端也會(huì)被重定向到新的主庫(kù),以繼續(xù)訪問(wèn)數(shù)據(jù)。
為了減少誤判,通常使用多個(gè)哨兵實(shí)例進(jìn)行部署,它們依據(jù)“多數(shù)原則”來(lái)判斷主庫(kù)的客觀下線情況。通常情況下,三個(gè)哨兵實(shí)例足以支持,只需兩個(gè)哨兵認(rèn)定主庫(kù)已客觀下線,切換過(guò)程將開(kāi)始。當(dāng)需要更高的判斷準(zhǔn)確性時(shí),可以考慮增加哨兵的數(shù)量,例如五個(gè)哨兵。
然而,通過(guò)多個(gè)哨兵實(shí)例來(lái)減少誤判可能引入新的挑戰(zhàn):
- 哨兵實(shí)例故障處理:哨兵集群中如果有實(shí)例發(fā)生故障,可能會(huì)影響主庫(kù)狀態(tài)判斷和主從切換。因此,需要有效地管理哨兵集群的高可用性。
- 主從切換決策:在多數(shù)哨兵實(shí)例達(dá)成共識(shí),認(rèn)定主庫(kù)客觀下線后,需要決定哪個(gè)哨兵實(shí)例來(lái)執(zhí)行主從切換。這涉及一些機(jī)制,如選舉,以確保選出的新主庫(kù)是最合適的。
要理解并應(yīng)對(duì)這些挑戰(zhàn),需要深入了解哨兵集群及其配置。這有助于確保 Redis 集群在面臨故障時(shí)仍然能夠提供穩(wěn)定的高可用性服務(wù)。在下一篇中,我們將深入探討哨兵集群的工作原理和相關(guān)問(wèn)題。