面試官:為什么 Redis 要有哨兵?
網站有讀者留言問:如何進行故障轉移?
所以,后面我就補充了這部分內容。
話不多說,發車!
為什么要有哨兵機制?
在 Redis 的主從架構中,由于主從模式是讀寫分離的,如果主節點(master)掛了,那么將沒有主節點來服務客戶端的寫操作請求,也沒有主節點給從節點(slave)進行數據同步了。
主節點掛了
這時如果要恢復服務的話,需要人工介入,選擇一個「從節點」切換為「主節點」,然后讓其他從節點指向新的主節點,同時還需要通知上游那些連接 Redis 主節點的客戶端,將其配置中的主節點 IP 地址更新為「新主節點」的 IP 地址。
這樣也不太“智能”了,要是有一個節點能監控「主節點」的狀態,當發現主節點掛了 ,它自動將一個「從節點」切換為「主節點」的話,那么可以節省我們很多事情??!
Redis 在 2.8 版本以后提供的哨兵(Sentinel)機制,它的作用是實現主從節點故障轉移。它會監測主節點是否存活,如果發現主節點掛了,它就會選舉一個從節點切換為主節點,并且把新主節點的相關信息通知給從節點和客戶端。
哨兵機制是如何工作的?
哨兵其實是一個運行在特殊模式下的 Redis 進程,所以它也是一個節點。從“哨兵”這個名字也可以看得出來,它相當于是“觀察者節點”,觀察的對象是主從節點。
當然,它不僅僅是觀察那么簡單,在它觀察到有異常的狀況下,會做出一些“動作”,來修復異常狀態。
哨兵節點主要負責三件事情:監控、選主、通知。
哨兵的職責
所以,我們重點要學習這三件事情:
- 哨兵節點是如何監控節點的?又是如何判斷主節點是否真的故障了?
- 根據什么規則選擇一個從節點切換為主節點?
- 怎么把新主節點的相關信息通知給從節點和客戶端呢?
如何判斷主節點真的故障了?
哨兵會每隔 1 秒給所有主從節點發送 PING 命令,當主從節點收到 PING 命令后,會發送一個響應命令給哨兵,這樣就可以判斷它們是否在正常運行。
哨兵監控主從節點
如果主節點或者從節點沒有在規定的時間內響應哨兵的 PING 命令,哨兵就會將它們標記為「主觀下線」。這個「規定的時間」是配置項 down-after-milliseconds 參數設定的,單位是毫秒。
主觀下線?難道還有客觀下線?
是的沒錯,客觀下線只適用于主節點。
之所以針對「主節點」設計「主觀下線」和「客觀下線」兩個狀態,是因為有可能「主節點」其實并沒有故障,可能只是因為主節點的系統壓力比較大或者網絡發送了擁塞,導致主節點沒有在規定時間內響應哨兵的 PING 命令。
所以,為了減少誤判的情況,哨兵在部署的時候不會只部署一個節點,而是用多個節點部署成哨兵集群(最少需要三臺機器來部署哨兵集群),通過多個哨兵節點一起判斷,就可以就可以避免單個哨兵因為自身網絡狀況不好,而誤判主節點下線的情況。同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,誤判率也能降低。
具體是怎么判定主節點為「客觀下線」的呢?
當一個哨兵判斷主節點為「主觀下線」后,就會向其他哨兵發起命令,其他哨兵收到這個命令后,就會根據自身和主節點的網絡狀況,做出贊成投票或者拒絕投票的響應。
當這個哨兵的贊同票數達到哨兵配置文件中的 quorum 配置項設定的值后,這時主節點就會被該哨兵標記為「客觀下線」。
例如,現在有 3 個哨兵,quorum 配置的是 2,那么一個哨兵需要 2 張贊成票,就可以標記主節點為“客觀下線”了。這 2 張贊成票包括哨兵自己的一張贊成票和另外兩個哨兵的贊成票。
PS:quorum 的值一般設置為哨兵個數的二分之一加1,例如 3 個哨兵就設置 2。
哨兵判斷完主節點客觀下線后,哨兵就要開始在多個「從節點」中,選出一個從節點來做新主節點。
由哪個哨兵進行主從故障轉移?
前面說過,為了更加“客觀”的判斷主節點故障了,一般不會只由單個哨兵的檢測結果來判斷,而是多個哨兵一起判斷,這樣可以減少誤判概率,所以哨兵是以哨兵集群的方式存在的。
問題來了,由哨兵集群中的哪個節點進行主從故障轉移呢?
所以這時候,還需要在哨兵集群中選出一個 leeder,讓 leeder 來執行主從切換。
選舉 leeder 的過程其實是一個投票的過程,在投票開始前,肯定得有個「候選者」。
那誰來作為候選者呢?
哪個哨兵節點判斷主節點為「客觀下線」,這個哨兵節點就是候選者,所謂的候選者就是想當 Leader 的哨兵。
舉個例子,假設有三個哨兵。當哨兵 B 先判斷到主節點「主觀下線后」,就會給其他實例發送 is-master-down-by-addr 命令。接著,其他哨兵會根據自己和主節點的網絡連接情況,做出贊成投票或者拒絕投票的響應。
當哨兵 B 收到贊成票數達到哨兵配置文件中的 quorum 配置項設定的值后,就會將主節點標記為「客觀下線」,此時的哨兵 B 就是一個Leader 候選者。
候選者如何選舉成為 Leader?
候選者會向其他哨兵發送命令,表明希望成為 Leader 來執行主從切換,并讓所有其他哨兵對它進行投票。
每個哨兵只有一次投票機會,如果用完后就不能參與投票了,可以投給自己或投給別人,但是只有候選者才能把票投給自己。
那么在投票過程中,任何一個「候選者」,要滿足兩個條件:
- 第一,拿到半數以上的贊成票;
- 第二,拿到的票數同時還需要大于等于哨兵配置文件中的 quorum 值。
舉個例子,假設哨兵節點有 3 個,quorum 設置為 2,那么任何一個想成為 Leader 的哨兵只要拿到 2 張贊成票,就可以選舉成功了。如果沒有滿足條件,就需要重新進行選舉。
這時候有的同學就會問了,如果某個時間點,剛好有兩個哨兵節點判斷到主節點為客觀下線,那這時不就有兩個候選者了?這時該如何決定誰是 Leader 呢?
每位候選者都會先給自己投一票,然后向其他哨兵發起投票請求。如果投票者先收到「候選者 A」的投票請求,就會先投票給它,如果投票者用完投票機會后,收到「候選者 B」的投票請求后,就會拒絕投票。這時,候選者 A 先滿足了上面的那兩個條件,所以「候選者 A」就會被選舉為 Leader。
為什么哨兵節點至少要有 3 個?
如果哨兵集群中只有 2 個哨兵節點,此時如果一個哨兵想要成功成為 Leader,必須獲得 2 票,而不是 1 票。
所以,如果哨兵集群中有個哨兵掛掉了,那么就只剩一個哨兵了,如果這個哨兵想要成為 Leader,這時票數就沒辦法達到 2 票,就無法成功成為 Leader,這時是無法進行主從節點切換的。
因此,通常我們至少會配置 3 個哨兵節點。這時,如果哨兵集群中有個哨兵掛掉了,那么還剩下兩個個哨兵,如果這個哨兵想要成為 Leader,這時還是有機會達到 2 票的,所以還是可以選舉成功的,不會導致無法進行主從節點切換。
當然,你要問,如果 3 個哨兵節點,掛了 2 個怎么辦?這個時候得人為介入了,或者增加多一點哨兵節點。
再說一個問題,Redis 1 主 4 從,5 個哨兵 ,quorum 設置為 3,如果 2 個哨兵故障,當主節點宕機時,哨兵能否判斷主節點“客觀下線”?主從能否自動切換?
- 哨兵集群可以判定主節點“客觀下線”。哨兵集群還剩下 3 個哨兵,當一個哨兵判斷主節點“主觀下線”后,詢問另外 2 個哨兵后,有可能能拿到 3 張贊同票,這時就達到了 quorum 的值,因此,哨兵集群可以判定主節點為“客觀下線”。
- 哨兵集群可以完成主從切換。當有個哨兵標記主節點為「客觀下線」后,就會進行選舉 Leader 的過程,因為此時哨兵集群還剩下 3 個哨兵,那么還是可以拿到半數以上(5/2+1=3)的票,而且也達到了 quorum 值,滿足了選舉 Leader 的兩個條件, 所以就能選舉成功,因此哨兵集群可以完成主從切換。
如果 quorum 設置為 2 ,并且如果有 3 個哨兵故障的話。此時哨兵集群還是可以判定主節點為“客觀下線”,但是哨兵不能完成主從切換了,大家可以自己推演下。
如果 quorum 設置為 3,并且如果有 3 個哨兵故障的話,哨兵集群即不能判定主節點為“客觀下線”,也不能完成主從切換了。
可以看到,quorum 為 2 的時候,并且如果有 3 個哨兵故障的話,雖然可以判定主節點為“客觀下線”,但是不能完成主從切換,這樣感覺「判定主節點為客觀下線」這件事情白做了一樣,既然這樣,還不如不要做,quorum 為 3 的時候,就可以避免這種無用功。
所以,quorum 的值建議設置為哨兵個數的二分之一加1,例如 3 個哨兵就設置 2,5 個哨兵設置為 3,而且哨兵節點的數量應該是奇數。
主從故障轉移的過程是怎樣的?
在哨兵集群中通過投票的方式,選舉出了哨兵 leader 后,就可以進行主從故障轉移的過程了,如下圖:
主從故障轉移操作包含以下四個步驟:
- 第一步:在已下線主節點(舊主節點)屬下的所有「從節點」里面,挑選出一個從節點,并將其轉換為主節點。
- 第二步:讓已下線主節點屬下的所有「從節點」修改復制目標,修改為復制「新主節點」;
- 第三步:將新主節點的 IP 地址和信息,通過「發布者/訂閱者機制」通知給客戶端;
- 第四步:繼續監視舊主節點,當這個舊主節點重新上線時,將它設置為新主節點的從節點;
步驟一:選出新主節點
故障轉移操作第一步要做的就是在已下線主節點屬下的所有「從節點」中,挑選出一個狀態良好、數據完整的從節點,然后向這個「從節點」發送 SLAVEOF no one 命令,將這個「從節點」轉換為「主節點」。
那么多「從節點」,到底選擇哪個從節點作為新主節點的?
隨機的方式好嗎?隨機的方式,實現起來很簡單,但是如果選到一個網絡狀態不好的從節點作為新主節點,那么可能在將來不久又要做一次主從故障遷移。
所以,我們首先要把網絡狀態不好的從節點給過濾掉。首先把已經下線的從節點過濾掉,然后把以往網絡連接狀態不好的從節點也給過濾掉。
怎么判斷從節點之前的網絡連接狀態不好呢?
Redis 有個叫 down-after-milliseconds * 10 配置項,其down-after-milliseconds 是主從節點斷連的最大連接超時時間。如果在 down-after-milliseconds 毫秒內,主從節點都沒有通過網絡聯系上,我們就可以認為主從節點斷連了。如果發生斷連的次數超過了 10 次,就說明這個從節點的網絡狀況不好,不適合作為新主節點。
至此,我們就把網絡狀態不好的從節點過濾掉了,接下來要對所有從節點進行三輪考察:優先級、復制進度、ID 號。在進行每一輪考察的時候,哪個從節點優先勝出,就選擇其作為新主節點。
- 第一輪考察:哨兵首先會根據從節點的優先級來進行排序,優先級越小排名越靠前,
- 第二輪考察:如果優先級相同,則查看復制的下標,哪個從「主節點」接收的復制數據多,哪個就靠前。
- 第三輪考察:如果優先級和下標都相同,就選擇從節點 ID 較小的那個。
第一輪考察:優先級最高的從節點勝出
Redis 有個叫 slave-priority 配置項,可以給從節點設置優先級。
每一臺從節點的服務器配置不一定是相同的,我們可以根據服務器性能配置來設置從節點的優先級。
比如,如果 「 A 從節點」的物理內存是所有從節點中最大的, 那么我們可以把「 A 從節點」的優先級設置成最高。這樣當哨兵進行第一輪考慮的時候,優先級最高的 A 從節點就會優先勝出,于是就會成為新主節點。
第二輪考察:復制進度最靠前的從節點勝出
如果在第一輪考察中,發現優先級最高的從節點有兩個,那么就會進行第二輪考察,比較兩個從節點哪個復制進度。
什么是復制進度?主從架構中,主節點會將寫操作同步給從節點,在這個過程中,主節點會用 master_repl_offset 記錄當前的最新寫操作在 repl_backlog_buffer 中的位置(如下圖中的「主服務器已經寫入的數據」的位置),而從節點會用 slave_repl_offset 這個值記錄當前的復制進度(如下圖中的「從服務器要讀的位置」的位置)。
如果某個從節點的 slave_repl_offset 最接近 master_repl_offset,說明它的復制進度是最靠前的,于是就可以將它選為新主節點。
第三輪考察:ID 號小的從節點勝出
如果在第二輪考察中,發現有兩個從節點優先級和復制進度都是一樣的,那么就會進行第三輪考察,比較兩個從節點的 ID 號,ID 號小的從節點勝出。
什么是 ID 號?每個從節點都有一個編號,這個編號就是 ID 號,是用來唯一標識從節點的。
到這里,選主的事情終于結束了。簡單給大家總結下:
在選舉出從節點后,哨兵 leader 向被選中的從節點發送 SLAVEOF no one 命令,讓這個從節點解除從節點的身份,將其變為新主節點。
如下圖,哨兵 leader 向被選中的從節點 server2 發送 SLAVEOF no one 命令,將該從節點升級為新主節點。
在發送 SLAVEOF no one 命令之后,哨兵 leader 會以每秒一次的頻率向被升級的從節點發送 INFO 命令(沒進行故障轉移之前,INFO 命令的頻率是每十秒一次),并觀察命令回復中的角色信息,當被升級節點的角色信息從原來的 slave 變為 master 時,哨兵 leader 就知道被選中的從節點已經順利升級為主節點了。
如下圖,選中的從節點 server2 升級成了新主節點:
步驟二:將從節點指向新主節點
當新主節點出現之后,哨兵 leader 下一步要做的就是,讓已下線主節點屬下的所有「從節點」指向「新主節點」,這一動作可以通過向「從節點」發送 SLAVEOF 命令來實現。
如下圖,哨兵 leader 向所有從節點(server3和server4)發送 SLAVEOF ,讓它們成為新主節點的從節點。
所有從節點指向新主節點后的拓撲圖如下:
步驟三:通知客戶的主節點已更換
經過前面一系列的操作后,哨兵集群終于完成主從切換的工作,那么新主節點的信息要如何通知給客戶端呢?
這主要通過 Redis 的發布者/訂閱者機制來實現的。每個哨兵節點提供發布者/訂閱者機制,客戶端可以從哨兵訂閱消息。
哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從節點切換過程中的不同關鍵事件,幾個常見的事件如下:
客戶端和哨兵建立連接后,客戶端會訂閱哨兵提供的頻道。主從切換完成后,哨兵就會向 +switch-master 頻道發布新主節點的 IP 地址和端口的消息,這個時候客戶端就可以收到這條信息,然后用這里面的新主節點的 IP 地址和端口進行通信了。
通過發布者/訂閱者機制機制,有了這些事件通知,客戶端不僅可以在主從切換后得到新主節點的連接信息,還可以監控到主從節點切換過程中發生的各個重要事件。這樣,客戶端就可以知道主從切換進行到哪一步了,有助于了解切換進度。
步驟四:將舊主節點變為從節點
故障轉移操作最后要做的是,繼續監視舊主節點,當舊主節點重新上線時,哨兵集群就會向它發送 SLAVEOF 命令,讓它成為新主節點的從節點,如下圖:
至此,整個主從節點的故障轉移的工作結束。
哨兵集群是如何組成的?
前面提到了 Redis 的發布者/訂閱者機制,那就不得不提一下哨兵集群的組成方式,因為它也用到了這個技術。
在我第一次搭建哨兵集群的時候,當時覺得很詫異。因為在配置哨兵的信息時,竟然只需要填下面這幾個參數,設置主節點名字、主節點的 IP 地址和端口號以及 quorum 值。
sentinel monitor <master-name> <ip> <redis-port> <quorum>
不需要填其他哨兵節點的信息,我就好奇它們是如何感知對方的,又是如何組成哨兵集群的?
后面才了解到,哨兵節點之間是通過 Redis 的發布者/訂閱者機制來相互發現的。
在主從集群中,主節點上有一個名為__sentinel__:hello的頻道,不同哨兵就是通過它來相互發現,實現互相通信的。
在下圖中,哨兵 A 把自己的 IP 地址和端口的信息發布到__sentinel__:hello 頻道上,哨兵 B 和 C 訂閱了該頻道。那么此時,哨兵 B 和 C 就可以從這個頻道直接獲取哨兵 A 的 IP 地址和端口號。然后,哨兵 B、C 可以和哨兵 A 建立網絡連接。
通過這個方式,哨兵 B 和 C 也可以建立網絡連接,這樣一來,哨兵集群就形成了。
哨兵集群會對「從節點」的運行狀態進行監控,那哨兵集群如何知道「從節點」的信息?
主節點知道所有「從節點」的信息,所以哨兵會每 10 秒一次的頻率向主節點發送 INFO 命令來獲取所有「從節點」的信息。
如下圖所示,哨兵 B 給主節點發送 INFO 命令,主節點接受到這個命令后,就會把從節點列表返回給哨兵。接著,哨兵就可以根據從節點列表中的連接信息,和每個從節點建立連接,并在這個連接上持續地對從節點進行監控。哨兵 A 和 C 可以通過相同的方法和從節點建立連接。
正式通過 Redis 的發布者/訂閱者機制,哨兵之間可以相互感知,然后組成集群,同時,哨兵又通過 INFO 命令,在主節點里獲得了所有從節點連接信息,于是就能和從節點建立連接,并進行監控了。
參考資料:
《Redis 核心技術與實戰》
《Redis 設計與實現》
總結
Redis 在 2.8 版本以后提供的哨兵(Sentinel)機制,它的作用是實現主從節點故障轉移。它會監測主節點是否存活,如果發現主節點掛了,它就會選舉一個從節點切換為主節點,并且把新主節點的相關信息通知給從節點和客戶端。
哨兵一般是以集群的方式部署,至少需要 3 個哨兵節點,哨兵集群主要負責三件事情:監控、選主、通知。
哨兵節點通過 Redis 的發布者/訂閱者機制,哨兵之間可以相互感知,相互連接,然后組成哨兵集群,同時哨兵又通過 INFO 命令,在主節點里獲得了所有從節點連接信息,于是就能和從節點建立連接,并進行監控了。
1.第一輪投票:判斷主節點下線
當哨兵集群中的某個哨兵判定主節點下線(主觀下線)后,就會向其他哨兵發起命令,其他哨兵收到這個命令后,就會根據自身和主節點的網絡狀況,做出贊成投票或者拒絕投票的響應。
當這個哨兵的贊同票數達到哨兵配置文件中的 quorum 配置項設定的值后,這時主節點就會被該哨兵標記為「客觀下線」。
2.第二輪投票:選出哨兵leader
某個哨兵判定主節點客觀下線后,該哨兵就會發起投票,告訴其他哨兵,它想成為 leader,想成為 leader 的哨兵節點,要滿足兩個條件:
第一,拿到半數以上的贊成票;
第二,拿到的票數同時還需要大于等于哨兵配置文件中的 quorum 值。
3.由哨兵 leader 進行主從故障轉移
選舉出了哨兵 leader 后,就可以進行主從故障轉移的過程了。該操作包含以下四個步驟:
第一步:在已下線主節點(舊主節點)屬下的所有「從節點」里面,挑選出一個從節點,并將其轉換為主節點,選擇的規則:
過濾掉已經離線的從節點;
過濾掉歷史網絡連接狀態不好的從節點;
將剩下的從節點,進行三輪考察:優先級、復制進度、ID 號。在每一輪考察過程中,如果找到了一個勝出的從節點,就將其作為新主節點。
第二步:讓已下線主節點屬下的所有「從節點」修改復制目標,修改為復制「新主節點」;
第三步:將新主節點的 IP 地址和信息,通過「發布者/訂閱者機制」通知給客戶端;
第四步:繼續監視舊主節點,當這個舊主節點重新上線時,將它設置為新主節點的從節點;