你真的懂 Redis 哨兵集群嗎?一主二從三哨兵架構如何扛住百萬級并發?
通過之前的學習,你已知道 Redis 主從復制是高可用的基石,某個 slave 宕機依然可以將請求發送給 master 或者其他 slave,但是如果 master 宕機,則只能響應讀操作,寫請求無法再執行。
所以主從復制架構面臨一個嚴峻問題:master 宕機,無法執行寫操作,無法自動選擇將一個 slave 切換為 master,也就是無法實現自動故障切換。
“Chaya:“還記得那晚我與男友約會,眼前是橡樹的綠葉,白色的竹籬笆。好想告訴我的他,這里像幅畫,一起手牽手么么噠(此處省略 10000 字)。
“Redis 忽然宕機,我的戀人小 Q 總不能把我推開,停止甜蜜,然后打開電腦手工進行主從切換,再通知其他程序員把地址改成新 master 的信息上線。”
如此一折騰,你心里的雨傾盆地下,萬萬使不得。所以必須有一個高可用的方案,為此,我提供一個高可用方案——哨兵(sentinel)。
哨兵是什么?
“吃瓜群眾:“Redis 大佬,雖然我沒有女朋友,但是未雨綢繆,我要掌握這個哨兵模式,防止當我與女朋友約會時被打擾,你快說說什么是哨兵以及哨兵的實現原理吧。”
先來看看哨兵是什么?搭建哨兵集群的方法我就不細說了,假設三個哨兵組成一個哨兵集群,三個數據節點構成一個一主兩從的 Redis 主從架構,如圖 3-17 所示。
圖3-17
Redis 哨兵集群高可用架構有三種角色,分別是 master、slave 和 sentinel。
- sentinel 之間互相通信,組成一個集群實現哨兵高可用,選舉出一個 leader 執行故障遷移操作。
- master 與 slave 之間通信,組成主從復制架構。
- sentinel 與 master/ slave 通信,是為了對該主從復制架構進行管理,包括監視(Monitoring)、通知(Notification)、自動故障切換(Automatic Failover)、配置提供者(Configuration Provider)。
哨兵監控的 master 的名字叫作 mymaster,master 的 IP 地址是 127.0.0.1,端口是 6379。quorum 是關鍵參數,它的作用如下。
- 指定在標記 master 故障并嘗試執行故障切換時需要一定數量達成一致意見的哨兵進程。大白話就是需要多少個哨兵進程認為 master 宕機,真正標記 master 宕機才能啟動故障切換過程。
- 對于多個哨兵,需要選出一個 leader 來執行實際的故障自動轉移操作,當某個哨兵的票數超過 quorum 時,就選舉這個哨兵為 leader,負責自動故障切換。quorum 的值一般取哨兵個數的一半以上 (n/2 + 1) 比較合理。
哨兵只要配置 master 信息即可與三個角色建立聯系。
“Chaya:“為什么哨兵只需要配置 master 信息就可以與三個角色建立聯系?”
- 哨兵可以通過 master 獲取 slave 的信息,并與 slave 建立連接。master 與 slave 是主從關系,通過 info 命令就可以通過 master 獲取 slave 的 IP 地址 和 port、runid 等信息。
- 通過上面的步驟,哨兵與 master 和所有的 slave 建立連接,哨兵之間的互相感知則通過 Redis 的發布/訂閱機制實現。每個哨兵通過發布/訂閱 master 的 sentinel:hello 頻道發布和接收信息,以此感知其他哨兵的存在并建立連接。
哨兵的任務
哨兵是 Redis 的一種運行模式,它專注于對 Redis 實例(master、slave)運行狀態的監控,并能夠在 master 發生故障時通過一系列的機制實現選主及主從切換,實現自動故障切換,確保整個 Redis 系統的可用性。
Chaya 可以安心地與愛人在歡樂港灣約會,盡情享受甜蜜,哪怕是吵架都那么醉人,不再需要擔心 Redis 忽然宕機帶來的煩惱。
我們先從全局看哨兵,簡要地了解它的整個運作流程,接著針對每個任務詳細分析,Redis 哨兵的主要職責如下。
- 監控(Monitoring):Redis 的哨兵不斷檢查 master 和 slave 實例是否按預期工作。它監視實例的健康狀態,包括 master 和所有 slave。
- 自動故障切換(Automatic Failover)**:如果 master 出現故障或不按預期工作,Redis 的哨兵則啟動自動故障切換流程。在此過程中,一個 slave 會被晉升為新的 master。
- 通知(Notification):讓 slave 執行 replicaof 命令與新的 master 同步數據;并且通知客戶端與新的 master 建立連接,如圖 3-18 所示。
- 配置提供者(Configuration Provide):哨兵充當了客戶端服務發現的權威來源。客戶端連接到任何一個哨兵以獲取新的 master 的地址,確保能夠連接到正確的實例。
1. 監控
Redis :八卦一下,Chaya,你的戀人用什么方式來了解你每天的喜怒哀樂呢?
“Chaya:“這很簡單,他每天給我發微信消息、打電話或者打視頻,若是哪天我不接電話,或者他發送微信消息時出現紅色感嘆號,就說明我把他拉黑了。”
哨兵與各個角色節點建立連接后,通過PING、INFO、PUBLISH / SUBSCRIBE命令來監控所有實例的健康狀態,當然,它不會說情話。
哨兵默認會以每秒一次的頻率向所有的 master、slave、哨兵發送 PING 命令,這個其實是一個心跳檢測,用于探測實例是否存活。
- PING:所有節點之間通過發送 PING 命令確認對方是否在線,默認每秒發送一次。
- I NFO:哨兵向 master、slave 發送該命令,用于獲取 slave 的詳細信息。
- PUBLISH / SUBSCRIBE:哨兵會訂閱 master 和 slave 的 sentinel:hello 頻道,并通過該頻道發布自己的信息,這樣其他哨兵之間就可以建立聯系。
如果一個 master 實例距離最后一次有效回復 PING 命令的時間超過 down-after- milliseconds 選項所指定的值,這個 master 實例就會被哨兵標記為“主觀下線”。
如果 slave 沒有在指定時間內響應哨兵的 PING 命令,則直接被標記為“主觀下線”。
只有當大于或等于法定個數(quorum)的哨兵節點認為該 master 主觀下線時,才能將該 master 改為客觀下線。接著才會開啟自動故障切換流程。
PING 命令的回復有兩種情況。
- 有效回復:返回 +PONG、-LOADING 和-MASTERDOWN 中的任何一種。
- 無效回復:有效回復之外的回復,或者不在指定時間內返回任何回復。
“Chaya:“主觀下線和客觀下線的作用是什么?”
主要是為了避免出現哨兵誤判 master 運行的情況,一旦出現誤判,就會出現 master 實際沒有下線,可是哨兵誤以為其已經下線的情況,接著就會啟動主從故障切換流程,之后的選主和通知操作都會消耗大量資源。
誤判一般會發生在集群網絡壓力較大、網絡擁塞或者是 master 本身壓力較大的情況下。
既然一個哨兵容易誤判,那就使用多個哨兵進行投票判斷。哨兵機制也是類似的,采用多實例組成的集群模式進行部署,就是哨兵集群。
引入多個哨兵實例一起進行判斷,就可以避免單個哨兵因為自身網絡狀況不好,而誤判主庫下線的情況。
同時,多個哨兵的網絡同時不穩定的概率較小,由它們一起做決策,也能降低誤判率。
主觀下線
主觀下線(Subjectively Down,SDOWN)指一個哨兵認為一個 Redis 實例已經不可用或者已經下線,這有可能是網絡不通、心跳超時或連接失敗等原因導致的。
例如對于 master 或者 slave,在 down-after-milliseconds 指定的毫秒數之內,如果沒有向哨兵發送的 PING 命令回復,或者返回一個錯誤,那么哨兵會將這個服務器標記為主觀下線。
需要注意的是,Redis 的哨兵的主要目標是確保 master 的高可用性,而不是 slave 的高可用性。
因此,主觀下線和客觀下線的主要關注點通常是 master。slave 通常不會被單獨標記為客觀下線,因為它們不承擔 master 的關鍵角色,它們的主要責任是復制數據。
客觀下線
判斷 master 是否下線不能只由一個哨兵說了算,只有過半的哨兵判斷 master 主觀下線,才能將 master 標記為客觀下線,如圖 3-19 所示。
3-19
之前提到過 sentinel monitor <master-name> <ip> <redis-port> <quorum> 的配置,參數 quorum 是判斷客觀下線的依據之一,意思是至少有 quorum 個哨兵判定這個 master 主觀下線,才會將這個 master 標記為客觀下線。
只有 master 被判定為客觀下線,才會進一步觸發哨兵執行主從切換流程。
2. 自動故障切換
“
Chaya:“一旦判斷 master 客觀下線,就在 slave 中選一個作為新的 master 嗎?”
哨兵的第二個任務是選擇一個 slave 作為新的 master,并對外提供服務。之后其他 slave 會與新的 master 進行主從復制,這個過程叫作自動故障切換,如圖 3-20 所示。
3-20
吃瓜群眾:“如何從眾多 slave 中選出一個做 master 呢?”
Chaya:“我覺得篩選過程就像找戀愛對象,每個人心中都有標尺,會通過直覺、習慣和自己的標準從所有的追求者中選擇一個最適合自己的。”
類似地,Redis 有自己的篩選規則,按照一定的篩選條件和打分策略,選出一個“節點”擔任 master。
篩選條件
“Chaya:“有哪些篩選條件?”
- 下線或網絡斷連的 slave 直接丟棄。
- 網絡無異常:slave 最后一次響應 PING 命令的時間不能超過 5 倍 PING 周期;slave INFO(每 10s 發送一次 INFO 命令)的信息更新時間不能超過 3 倍 INFO 刷新周期。
- 評估過往的網絡狀態:slave 與 master 斷開連接,斷連時間不能超過(現在-master 被標記為下線的時間)+(master 的 down-after- milliseconds 配置項的值乘以 10),單位是毫秒。
總之,下線或者網絡經常斷開的 slave 不能要。如果新的 master 很快出現網絡故障,就又得重新選擇新的 master,這不“鬧著玩”嗎,得排除掉!
打分
過濾掉不合適的 slave 之后,使用快速排序對 slave 列表進行打分,按照以下排序找出“王者”。
- slave 優先級:通過 replica-priority 100 配置項,給不同的 slave 配置不同優先級,默認是 100,值越低,優先級越高,配置為特殊值 0 表示不會晉升為 master。
- 更大復制偏移量(processed replication offset):已復制的數據量越多,slave_repl_offset 與 master_repl_offset 的差值就越小。
- slave runID:在優先級和復制進度都相同的情況下,runID 最小的 slave 得分最高,該 slave 會被選為新的 master。
哨兵向篩選出來的 slave 發送 slave no one 命令,使得該 slave 成為新的 master,哨兵并不關心命令返回的結果,它會發送 info 命令給 slave,并根據命令的回復內容確認 slave 是否成功轉換為 master。
“Chaya:“舊的 master 重新恢復正常時要怎么處理?”
舊的不去,新的不來,有些人一旦錯過就不在,既然已經錯過,相逢也只能是過客。
原 master 恢復正常,重新連接哨兵,這時集群已經有新的 master,所以舊的 master 被哨兵降級為 slave。
3. 通知
新的 master 出現后,哨兵還有一件重要的事情要做——將新的 master 的連接信息通過 slave 命令發送給其他 slave,通知 slave 執行 replacaof 命令和新的 master 建立連接進行主從復制。
接著,哨兵會定時給 slave 發 INFO 命令,從 INFO 命令的回復內容來確認 slave 是否與新的 master 成功建立連接。檢測到所有 slave 都與新的 master 建立連接,自動故障切換就完成了。
如果還有剩余 slave 沒有連上新的 master,則哨兵還會再做一次努力,再次向這些 slave 發送 slave 命令,要求他們與新的 master 建立連接。
4. 配置提供者
Redis 客戶端只需要跟哨兵打交道,就可以無感知地連接到新的 master,最重要的原因是哨兵提供了一些 API 來檢查主從節點的運行狀況。
哨兵集群實現原理
如果只有一個哨兵就會存在單點故障問題。Redis sentinel 是一個分布式系統,由多個哨兵協作組成集群實現高可用。
- 當多個哨兵達成一致認為某個 master 不可用時,才執行故障遷移,降低了誤報的概率。
- 不需要所有哨兵都可用,哨兵集群依然可以正常工作。
“
Chaya:“哨兵是如何感知其他哨兵節點的呢?又如何知道 slave 節點的信息并監控呢?當 master 不可用時,到底由哪個哨兵來執行自動故障切換呢?”
1. 發布/訂閱機制
哨兵互相發現
哨兵之間可以互相感知發現,這歸功于 Redis 的發布/訂閱機制。
當哨兵與 master 建立連接后,使用發布/訂閱機制在特殊的頻道發布自己的信息,例如 IP 地址和端口,同時訂閱該頻道獲取其他哨兵發布的消息。
master 有一個 sentinel:hello 的專用通道,用于哨兵之間發布和訂閱消息。
可以比喻為哨兵利用 master 建立的sentinel:hello 微信群發布自己的消息,同時關注其他哨兵發布的消息,如圖 3-21 所示。
3-21
哨兵如何感知并監控 slave
哨兵之間建立連接形成集群還不夠,哨兵還需要跟所有 slave 建立連接,否則無法監控它們。除此之外,如果發生了主從切換也需要通知 slave 重新與新的 master 建立連接進行數據同步。
哨兵向 master 發送 INFO 命令,master 接收到命令后,將 slave 列表告訴哨兵。
哨兵根據 master 響應的 slave 名單信息與所有 salve 建立連接,并且根據這個連接持續監控 slave,剩下的哨兵也基于此實現監控,如圖 3-22 示。
2. 選擇哨兵執行主從切換
“Chaya:“master 不可用后,如何選擇一個哨兵來執行自動故障切換呢?”
任何哨兵判斷 master 主觀下線后,都會向其他哨兵發送 is-master-down-by- addr 命令,其他哨兵收到命令后則根據自己與 master 之間的連接狀況分別響應 Y 或者 N,Y 表示贊成,N 表示反對。
如果某個哨兵獲得了大多數哨兵的贊成票,就標記 master 為客觀下線。
例如,一共有 3 個哨兵組成集群,那么 quorum 就可以配置為 2,當一個哨兵獲得了 2 張贊成票(包含自己的 1 票)時,就可以標記 master“客觀下線”。
獲得多數贊成票的哨兵向其他哨兵發送 SENTINEL is-master-down-by-addr <masterip> <masterport> <sentinel.current_epoch>
命令,聲明自己想要執行主從切換并開始拉票。
其他哨兵則進行投票,投票過程叫作 leader 選舉,選舉的過程借鑒了分布式系統中的 Raft 協議。
簡單地說,哨兵標記當前 master 客觀下線后,通過投票的方式從哨兵集群中選舉出一個哨兵作為 leader 角色執行故障切換。
“Chaya:“我發現判斷 master 是否客觀下線和哨兵拉票選舉 leader 是同樣的命令。”
沒錯,is-master-down-by-addr 命令有兩個作用:
- 一是詢問其他哨兵是否認為某個 master 已經主觀下線;
- 二是開始進行自動故障切換時,當前哨兵向其他哨兵實例進行"拉票",讓其他哨兵選舉自己為 leader。
哨兵想要成為 leader 沒那么簡單,得有兩把“刷子”。需要滿足以下條件。
- 獲得其他哨兵過半的投票。
- 投票的數量大于或等于 quorum 的值。
如果 sentine 集群有 2 個實例,此時,一個哨兵要想成為 leader,那么必須獲得 2 票,而不是 1 票。
所以,如果有一個哨兵宕機了,那么此時的集群是無法進行主從庫切換的。因此,通常我們至少會配置 3 個哨兵實例。
3. 發布/訂閱機制
在 Redis 中,發布/訂閱(Pub/Sub)機制發布不同事件,讓客戶端訂閱消息。哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關鍵事件。
master 相關
與 master 相關的消息訂閱頻道。
◎ +sdown:節點處于主觀下線狀態。
◎ -sdown:節點不再處于主觀下線狀態。
◎ +odown:節點進入客觀下線狀態。
◎ -odown:節點退出客觀下線狀態。
◎ +switch-master:master 地址發生了變化。
slave 相關
與 slave 相關的消息訂閱頻道。
◎ +slave-reconf-sent:leader 哨兵發送 REPLICAOF 命令重新配置從庫。
◎ +slave-reconf-inprog:slave 配置了新的 master,但是尚未同步。
◎ +slave-reconf-done:slave 配置了新的 master,并完成了數據同步。
Redis 的發布/訂閱機制尤其重要,有了發布/訂閱機制,哨兵和哨兵之間、哨兵和 slave 之間、哨兵和客戶端之間就都能建立起連接了,各種事件的發布也是通過這個機制實現的。