我們都了解redis主從模式。在這個模式下,如果從庫發(fā)生故障了,客戶端可以繼續(xù)向主庫或其他從庫發(fā)送請求,進行相關的操作,但是如果主庫發(fā)生故障了,那就直接會影響到從庫的同步,因為從庫沒有相應的主庫可以進行數(shù)據(jù)復制操作了。
而且,如果客戶端發(fā)送的都是讀操作請求,那還可以由從庫繼續(xù)提供服務,這在純讀的業(yè)務場景下還能被接受。但是,一旦有寫操作請求了,按照主從庫模式下的讀寫分離要求,需要由主庫來完成寫操作。此時,也沒有實例可以來服務客戶端的寫操作請求了,如下圖所示:

無論是寫服務中斷,還是從庫無法進行數(shù)據(jù)同步,都是不能接受的。所以,如果主庫掛了,我們就需要運行一個新主庫,比如說把一個從庫切換為主庫,把它當成主庫。這就涉及到三個問題:
- 主庫真的掛了嗎?
- 該選擇哪個從庫作為主庫?
- 怎么把新主庫的相關信息通知給從庫和客戶端呢?
這就要提到哨兵機制了。在 Redis 主從集群中,哨兵機制是實現(xiàn)主從庫自動切換的關鍵機制,它有效地解決了主從復制模式下故障轉移的這三個問題。
哨兵機制的基本流程
哨兵其實就是一個運行在特殊模式下的 Redis 進程,主從庫實例運行的同時,它也在運行。哨兵主要負責的就是三個任務:監(jiān)控、選主(選擇主庫)和通知。
我們先看監(jiān)控。監(jiān)控是指哨兵進程在運行時,周期性地給所有的主從庫發(fā)送 PING 命令,檢測它們是否仍然在線運行。如果從庫沒有在規(guī)定時間內(nèi)響應哨兵的 PING 命令,哨兵就會把它標記為“下線狀態(tài)”;同樣,如果主庫也沒有在規(guī)定時間內(nèi)響應哨兵的 PING 命令,哨兵就會判定主庫下線,然后開始自動切換主庫的流程。
這個流程首先是執(zhí)行哨兵的第二個任務,選主。主庫掛了以后,哨兵就需要從很多個從庫里,按照一定的規(guī)則選擇一個從庫實例,把它作為新的主庫。這一步完成后,現(xiàn)在的集群里就有了新主庫。
然后,哨兵會執(zhí)行最后一個任務:通知。在執(zhí)行通知任務時,哨兵會把新主庫的連接信息發(fā)給其他從庫,讓它們執(zhí)行 replicaof 命令,和新主庫建立連接,并進行數(shù)據(jù)復制。同時,哨兵會把新主庫的連接信息通知給客戶端,讓它們把請求操作發(fā)到新主庫上。
我畫了一張圖片,展示了這三個任務以及它們各自的目標。

在這三個任務中,通知任務相對來說比較簡單,哨兵只需要把新主庫信息發(fā)給從庫和客戶端,讓它們和新主庫建立連接就行,并不涉及決策的邏輯。但是,在監(jiān)控和選主這兩個任務中,哨兵需要做出兩個決策:
- 在監(jiān)控任務中,哨兵需要判斷主庫是否處于下線狀態(tài);
- 在選主任務中,哨兵也要決定選擇哪個從庫實例作為主庫。
接下來,我們就先說說如何判斷主庫的下線狀態(tài)。
你首先要知道的是,哨兵對主庫的下線判斷有“主觀下線”和“客觀下線”兩種。那么,為什么會存在兩種判斷呢?它們的區(qū)別和聯(lián)系是什么呢?
主觀下線和客觀下線
哨兵進程會使用 PING 命令檢測它自己和主、從庫的網(wǎng)絡連接情況,用來判斷實例的狀態(tài)。如果哨兵發(fā)現(xiàn)主庫或從庫對 PING 命令的響應超時了,那么,哨兵就會先把它標記為“主觀下線”。
如果檢測的是從庫,那么,哨兵簡單地把它標記為“主觀下線”就行了,因為從庫的下線影響一般不太大,集群的對外服務不會間斷。
但是,如果檢測的是主庫,那么,哨兵還不能簡單地把它標記為“主觀下線”,開啟主從切換。因為很有可能存在這么一個情況:那就是哨兵誤判了,其實主庫并沒有故障。可是,一旦啟動了主從切換,后續(xù)的選主和通知操作都會帶來額外的計算和通信開銷。
為了避免這些不必要的開銷,我們要特別注意誤判的情況。
首先,我們要知道啥叫誤判。很簡單,就是主庫實際并沒有下線,但是哨兵誤以為它下線了。誤判一般會發(fā)生在集群網(wǎng)絡壓力較大、網(wǎng)絡擁塞,或者是主庫本身壓力較大的情況下。
一旦哨兵判斷主庫下線了,就會開始選擇新主庫,并讓從庫和新主庫進行數(shù)據(jù)同步,這個過程本身就會有開銷,例如,哨兵要花時間選出新主庫,從庫也需要花時間和新主庫同步。而在誤判的情況下,主庫本身根本就不需要進行切換的,所以這個過程的開銷是沒有價值的。正因為這樣,我們需要判斷是否有誤判,以及減少誤判。
那怎么減少誤判呢?在日常生活中,當我們要對一些重要的事情做判斷的時候,經(jīng)常會和家人或朋友一起商量一下,然后再做決定。
哨兵機制也是類似的,它通常會采用多實例組成的集群模式進行部署,這也被稱為哨兵集群。引入多個哨兵實例一起來判斷,就可以避免單個哨兵因為自身網(wǎng)絡狀況不好,而誤判主庫下線的情況。同時,多個哨兵的網(wǎng)絡同時不穩(wěn)定的概率較小,由它們一起做決策,誤判率也能降低。
如何選定新主庫?
一般來說,我把哨兵選擇新主庫的過程稱為“篩選 + 打分”。簡單來說,我們在多個從庫中,先按照一定的篩選條件,把不符合條件的從庫去掉。然后,我們再按照一定的規(guī)則,給剩下的從庫逐個打分,將得分最高的從庫選為新主庫,如下圖所示:

第一輪:優(yōu)先級最高的從庫得分高。
用戶可以通過 slave-priority 配置項,給不同的從庫設置不同優(yōu)先級。比如,你有兩個從庫,它們的內(nèi)存大小不一樣,你可以手動給內(nèi)存大的實例設置一個高優(yōu)先級。在選主時,哨兵會給優(yōu)先級高的從庫打高分,如果有一個從庫優(yōu)先級最高,那么它就是新主庫了。如果從庫的優(yōu)先級都一樣,那么哨兵開始第二輪打分。
第二輪:和舊主庫同步程度最接近的從庫得分高。
這個規(guī)則的依據(jù)是,如果選擇和舊主庫同步最接近的那個從庫作為主庫,那么,這個新主庫上就有最新的數(shù)據(jù)。
第三輪:ID 號小的從庫得分高。
每個實例都會有一個 ID,這個 ID 就類似于這里的從庫的編號。目前,Redis 在選主庫時,有一個默認的規(guī)定:在優(yōu)先級和復制進度都相同的情況下,ID 號最小的從庫得分最高,會被選為新主庫。
總結
哨兵機制,它是實現(xiàn) Redis 不間斷服務的重要保證。具體來說,主從集群的數(shù)據(jù)同步,是數(shù)據(jù)可靠的基礎保證;而在主庫發(fā)生故障時,自動的主從切換是服務不間斷的關鍵支撐。
Redis 的哨兵機制自動完成了以下三大功能,從而實現(xiàn)了主從庫的自動切換,可以降低 Redis 集群的運維開銷:
- 監(jiān)控主庫運行狀態(tài),并判斷主庫是否客觀下線;
- 在主庫客觀下線后,選取新主庫;
- 選出新主庫后,通知從庫和客戶端。