Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理
概要
我們知道「主從復(fù)制是高可用的基石」,從庫宕機(jī)依然可以將請求發(fā)送給主庫或者其他從庫,但是 Master 宕機(jī),只能響應(yīng)讀操作,寫請求無法再執(zhí)行。
所以主從復(fù)制架構(gòu)面臨一個嚴(yán)峻問題,主庫掛了,無法執(zhí)行「寫操作」,無法自動選擇一個 Slave 切換為 Master,也就是無法故障自動切換。
深夜與女朋友么么噠……(此處省略 10000 字),突然宕機(jī),總不能提起褲子從床上爬起來手工進(jìn)行主從切換,再通知其他程序員把地址重新改成新主庫上線。
如此一折騰自己已被女友切換成前男友了,萬萬使不得。所以我們必須有一個高可用的方案,為此,Redis 官方提供一個高可用方案——哨兵(Sentinel)。
Redis 哨兵集群原理?
開篇寄語
“
技術(shù)的迭代非常的快,但是從技術(shù)中沉淀下來的思維卻是受益終生的。所以不要擔(dān)心什么中年危機(jī),那些擔(dān)心中年危機(jī)的人通常很難成長起來。只要我們成長,只要我們的認(rèn)知在不斷突破,就不用擔(dān)心中年危機(jī),這個世界始終是需要那些優(yōu)秀人才的。
”
?
什么是哨兵(Sentinel)
“
65 哥:碼哥,雖然我沒女朋友,但是,未雨綢繆我要掌握這個哨兵模式,防止當(dāng)深夜與女朋友么么噠被打擾,你快說說哨兵的實現(xiàn)原理吧。
”
搭建實例采用三個哨兵形成集群,三個數(shù)據(jù)節(jié)點(一主兩從)方式搭建,如下圖所示:
Redis哨兵集群65 哥你聽過「武當(dāng)派」創(chuàng)始人張三瘋么?Redis 主從架構(gòu)就好比一個武當(dāng),掌門人就是 Master。掌門人如果掛了,需要從武當(dāng)七俠里面選舉能人擔(dān)當(dāng)掌門人。這就需要一個部門能監(jiān)控掌門人的生死和武當(dāng)其他弟子的生命狀態(tài),并且能夠通過投票從武當(dāng)?shù)茏又羞x舉一個能者擔(dān)任新掌門,接著再舉行新聞發(fā)布會向世界宣布新掌門的信息。這個「部門」就是哨兵。
哨兵在選舉新掌門會遇到以下幾個問題:
如何判斷掌門真的掛了,有可能假死;
到底選擇哪一個武當(dāng)子弟作為新掌門?
通過新聞發(fā)布會將新掌門的相關(guān)信息通知到所有武當(dāng)?shù)茏?#xff08;slave 和 master)和整個武林(客戶端)。
哨兵部門主要負(fù)責(zé)的任務(wù)是:監(jiān)控整個武當(dāng)、選擇新掌門,通知整個武當(dāng)和整個武林。
?
哨兵機(jī)制的主要任務(wù)
哨兵是 Redis 的一種運行模式,它專注于對 Redis 實例(主節(jié)點、從節(jié)點)運行狀態(tài)的監(jiān)控,并能夠在主節(jié)點發(fā)生故障時通過一系列的機(jī)制實現(xiàn)選主及主從切換,實現(xiàn)故障轉(zhuǎn)移,確保整個 Redis 系統(tǒng)的可用性。結(jié)合 Redis 的 官方文檔:https://redis.io/topics/sentinel,可以知道 Redis 哨兵具備的能力有如下幾個:
監(jiān)控:持續(xù)監(jiān)控 master 、slave 是否處于預(yù)期工作狀態(tài)。
自動切換主庫:當(dāng) Master 運行故障,哨兵啟動自動故障恢復(fù)流程:從 slave 中選擇一臺作為新 master。
通知:讓 slave 執(zhí)行 replicaof ,與新的 master 同步;并且通知客戶端與新 master 建立連接。
哨兵也是一個 Redis 進(jìn)程,只是不對外提供讀寫服務(wù),通常哨兵要配置成單數(shù),為啥呢?且聽「碼哥字節(jié)」慢慢分析。
“
65 哥:那到底「哨兵」這個神秘部門是如何實現(xiàn)這三個能力的?
”
我們先從全局觀看哨兵,簡要的了解整個運作流程,接著再針對每一個任務(wù)詳細(xì)分析。首先從監(jiān)控開始…...
監(jiān)控
Sentinel 只是武當(dāng)?shù)茏又械奶厥獠块T,在默認(rèn)情況下,Sentinel 通過飛鴿傳書以每秒一次的頻率向所有武當(dāng)?shù)茏印⒄崎T與哨兵(包括 Master、Slave、其他 Sentinel 在內(nèi))發(fā)送 PING 命令,如果 slave 沒有在在規(guī)定時間內(nèi)響應(yīng)「哨兵」的 PING 命令,「哨兵」就認(rèn)為這哥們可能嗝屁了,就會將他記錄為「下線狀態(tài)」;
假如 master 掌門沒有在規(guī)定時間響應(yīng) 「哨兵」的 PING 命令,哨兵就判定掌門下線,開始執(zhí)行「自動切換 master 掌門」的流程。
PING 命令的回復(fù)有兩種情況:
有效回復(fù):返回 +PONG、-LOADING、-MASTERDOWN 任何一種;
無效回復(fù):有效回復(fù)之外的回復(fù),或者指定時間內(nèi)返回任何回復(fù)。
“
65 哥:哨兵如何判斷「掌門」嗝屁呢?掌門詐尸咋辦?
”
為了防止掌門「假死」,「哨兵」設(shè)計了「主觀下線」和「客觀下線」兩種暗號。
主觀下線
哨兵利用 PING 命令來檢測掌門、 slave 的生命狀態(tài)。如果是無效回復(fù),哨兵就把這個哥們標(biāo)記為「主觀下線」。檢測到的是武當(dāng)小弟,也就是 slave 角色。那么就直接標(biāo)記「主觀下線」。
因為 master 掌門還在,slave 的嗝屁對整個武當(dāng)影響不大。依然可以對外開會,比武論劍、吃香喝辣…...
如果檢測到是 master 掌門完蛋,這時候哨兵不能這么簡單的標(biāo)記「主觀下線」,開啟新掌門選舉。
因為有可能出現(xiàn)誤判,掌門并沒有嗝屁,一旦啟動了掌門切換,后續(xù)的選主、通知開發(fā)布會,slave 花時間與新 master 同步數(shù)據(jù)都會消耗大量資源。
所以「哨兵」要降低誤判的概率,誤判一般會發(fā)生在集群網(wǎng)絡(luò)壓力較大、網(wǎng)絡(luò)擁塞,或者是主庫本身壓力較大的情況下。
既然一個人容易誤判,那就多個人一起投票判斷。哨兵機(jī)制也是類似的,采用多實例組成的集群模式進(jìn)行部署,這就是哨兵集群。引入多個哨兵實例一起來判斷,就可以避免單個哨兵因為自身網(wǎng)絡(luò)狀況不好,而誤判主庫下線的情況。
同時,多個哨兵的網(wǎng)絡(luò)同時不穩(wěn)定的概率較小,由它們一起做決策,誤判率也能降低。
客觀下線
判斷 master 是否下線不能只有一個「哨兵」說了算,只有過半的哨兵判斷 master 已經(jīng)「主觀下線」,這時候才能將 master 標(biāo)記為「客觀下線」,也就是說這是一個客觀事實,掌門真的嗝屁了,華佗再世也治不好了。
只有 master 被判定為「客觀下線」,才會進(jìn)一步觸發(fā)哨兵開始主從切換流程。
客觀下線主觀下線與客觀下線的區(qū)別
簡單來說,主觀下線是哨兵自己認(rèn)為節(jié)點宕機(jī),而客觀下線是不但哨兵自己認(rèn)為節(jié)點宕機(jī),而且該哨兵與其他哨兵溝通后,達(dá)到一定數(shù)量的哨兵都認(rèn)為該哥們嗝屁了。
這里的「一定數(shù)量」是一個法定數(shù)量(Quorum),是由哨兵監(jiān)控配置決定的,解釋一下該配置:
#?sentinel?monitor?<master-name>?<master-host>?<master-port>?<quorum> #?舉例如下: sentinel?monitor?mymaster?127.0.0.1?6379?2這條配置項用于告知哨兵需要監(jiān)聽的主節(jié)點:
sentinel monitor:代表監(jiān)控。
mymaster:代表主節(jié)點的名稱,可以自定義。
192.168.11.128:代表監(jiān)控的主節(jié)點 ip,6379 代表端口。
2:法定數(shù)量,代表只有兩個或兩個以上的哨兵認(rèn)為主節(jié)點不可用的時候,才會把 master 設(shè)置為客觀下線狀態(tài),然后進(jìn)行 failover 操作。
「客觀下線」的標(biāo)準(zhǔn)就是,當(dāng)有 N 個哨兵實例時,要有 N/2 + 1 個實例判斷 master 為「主觀下線」,才能最終判定 Master 為「客觀下線」,其實就是過半機(jī)制。
自動切換主庫
“
65 哥:既然判斷 master 客觀下線了,那就要從選出一個新掌門人了吧。
”
「哨兵」的第二個任務(wù),選擇新 master 掌門。需要從武當(dāng)?shù)茏又邪凑找欢ㄒ?guī)則選擇一個牛逼人物作為新掌門,完成選任掌門后,新 master 帶領(lǐng)眾弟子一起吃香喝辣。
按照一定的 「篩選條件」 + 「打分」 策略,選出「最強王者」擔(dān)任掌門,也就是通過一些條件海選過濾一些「無能之輩」,接著將通過海選的靚仔全都打分排名,將最高者選為新 master。
如圖所示:
新master選擇網(wǎng)絡(luò)經(jīng)常斷開的靚仔也不可取,你想,即使變成 master,可是很快網(wǎng)絡(luò)出了故障,又得重新選擇新 master,這不鬧著玩么,得排除掉!
篩選條件
“
65 哥:那都有哪些篩選條件呀?
”
從庫當(dāng)前在線狀態(tài),下線的直接丟棄;
評估之前的網(wǎng)絡(luò)連接狀態(tài) down-after-milliseconds \* 10:如果從庫總是和主庫斷連,而且斷連次數(shù)超出了一定的閾值(10 次),我們就有理由相信,這個從庫的網(wǎng)絡(luò)狀況并不是太好,就可以把這個從庫篩掉了。
打分
過濾掉不合適的 slave 之后,則進(jìn)入打分環(huán)節(jié)。打分會按照三個規(guī)則進(jìn)行三輪打分,規(guī)則分別為:
slave 優(yōu)先級,通過 slave-priority 配置項,給不同的從庫設(shè)置不同優(yōu)先級(后臺有人沒辦法),優(yōu)先級高的直接晉級為新 master 掌門。
slave_repl_offset與 master_repl_offset進(jìn)度差距(誰的武功與之前掌門的功夫越接近誰就更牛逼),如果都一樣,那就繼續(xù)下一個規(guī)則。其實就是比較 slave 與舊 master 復(fù)制進(jìn)度的差距;
slave runID,在優(yōu)先級和復(fù)制進(jìn)度都相同的情況下,ID 號最小的從庫得分最高,會被選為新主庫。(論資排輩,根據(jù) runID 的創(chuàng)建時間來判斷,時間早的上位);
通知
“
65 哥:為啥還要召開新聞發(fā)布會呢?
”
重新選舉新 master 掌門這種事情,何等大事,怎能不告知天下。再者其他 slave 弟子也要知道新掌門是誰,一起追隨新掌門吃香喝辣大保健。
最后一個任務(wù),「哨兵」將新 「master 掌門」的連接信息發(fā)送給其他 slave 武當(dāng)?shù)茏?#xff0c;并且讓 slave 執(zhí)行 replacaof 命令,和新「master 掌門」建立連接,并進(jìn)行數(shù)據(jù)復(fù)制學(xué)習(xí)新掌門的所有武功。
除此之外,「哨兵」還需要將新掌門的連接信息通知整個武林(客戶端),使得讓所有想拜訪、討教的人能找到新任掌門,這樣諸多事宜才能交給新掌門做決定(將讀寫請求轉(zhuǎn)移到新 master)。
哨兵的主要任務(wù)與實現(xiàn)目標(biāo)
哨兵執(zhí)行任務(wù)與目標(biāo)?
哨兵集群工作原理
「哨兵」部門并不是一個人,多個人共同組成一個「哨兵集群」,即使有一些「哨兵」被老王打死了,其他的「哨兵」依然可以共同協(xié)作完成監(jiān)控、新掌門選舉以及通知 slave 、master 以及每一個武林人士(客戶端)。
在配置哨兵集群的時候,哨兵配置中只設(shè)置了監(jiān)控的 master IP 和 port,并沒有配置其他哨兵的連接信息。
sentinel?monitor?<master-name>?<ip>?<redis-port>?<quorum>哨兵之間是如何知道彼此的?如何知道 slave 并監(jiān)控他們的?由哪一個「哨兵」執(zhí)行主從切換呢?
帶著這些問題,跟著「碼哥字節(jié)」一起追本溯源,深入哨兵集群心臟。
pub/sub 實現(xiàn)哨兵間通信和發(fā)現(xiàn) slave
“
65 哥:哨兵之間是如何知道彼此的?
”
哨兵之間可以相互通信約會搞事情,主要歸功于 Redis 的 pub/sub 發(fā)布/訂閱機(jī)制。
哨兵與 master 建立通信,利用 master 提供發(fā)布/訂閱機(jī)制發(fā)布自己的信息,比如身高體重、是否單身、IP、端口……
master 有一個 __sentinel__:hello 的專用通道,用于哨兵之間發(fā)布和訂閱消息。這就好比是 __sentinel__:hello 微信群,哨兵利用 master 建立的微信群發(fā)布自己的消息,同時關(guān)注其他哨兵發(fā)布的消息。
Redis pub/sub 機(jī)制當(dāng)多個哨兵實例都在主庫上做了發(fā)布和訂閱操作后,它們之間就能知道彼此的 IP 地址和端口,從而相互發(fā)現(xiàn)建立連接。
Redis 通過頻道的方式對消息進(jìn)行分別管理,這里的頻道其實就是不同的微信群。比如“碼哥字節(jié)讀者技術(shù)群”就是專門分享技術(shù)的群。朋友們可以關(guān)注公眾號,后臺回復(fù)“加群”,一起成長。
“
65 哥:哨兵之間雖然建立連接了,但是還需要和 slave 建立連接,不然沒法監(jiān)控他們呀,如何知道 slave 并監(jiān)控他們的?
”
的確,哨兵之間建立連接形成集群還不夠,還需要跟 slave 建立連接,不然沒法監(jiān)控他們,無法對主從庫進(jìn)行心跳判斷。
除此之外,如果發(fā)生了主從切換也得通知 slave 重新跟新 master 建立連接執(zhí)行數(shù)據(jù)同步。
關(guān)鍵還是利用 master 來實現(xiàn),哨兵向 master 發(fā)送 INFO 命令, master 掌門自然是知道自己門下所有的 salve 小弟的。所以 master 接收到命令后,便將 slave 列表告訴哨兵。
哨兵根據(jù) master 響應(yīng)的 slave 名單信息與每一個 salve 建立連接,并且根據(jù)這個連接持續(xù)監(jiān)控哨兵。
如圖所示,哨兵 2 向 Master 發(fā)送 INFO 命令,Master 就把 slave 列表返回給哨兵 2,哨兵 2 便根據(jù) slave 列表連接信息與每一個 slave 建立連接,并基于此連接實現(xiàn)持續(xù)監(jiān)控。
剩下的哨兵也同理基于此實現(xiàn)監(jiān)控。
INFO命令獲取slave信息選擇哨兵執(zhí)行主從切換
“
65 哥:master 嗝屁了以后,哨兵這么多,那到底讓哪一個哨兵來執(zhí)行新 master 切換呢?
”
這個跟哨兵判斷 master “客觀下線”類似,也是通過投票的方式選出來的。
任何一個哨兵判斷 master “主觀下線”后,就會給其他哨兵基友發(fā)送 is-master-down-by-addr 命令,好基友則根據(jù)自己跟 master 之間的連接狀況分別響應(yīng) Y 或者 N ,Y 表示贊成票, N 就是反對。
如果某個哨兵獲得了大多數(shù)哨兵的“贊成票”之后,就可以標(biāo)記 master 為 “客觀下線”,贊成票數(shù)是通過哨兵配置文件中的 quorum 配置項設(shè)定。
sentinel?monitor?<master-name>?<ip>?<redis-port>?<quorum>比如一共 3 個哨兵組成集群,那么 quorum 就可以配置成 2,當(dāng)一個哨兵獲得了 2 張贊成票,就可以標(biāo)記 master “客觀下線”,當(dāng)然這個票包含自己的那一票。
獲得多數(shù)贊成票的哨兵可以向其他哨兵發(fā)送命令,申明自己想要執(zhí)行主從切換。并讓其他哨兵進(jìn)行投票,投票過程就叫做 “Leader 選舉”。
想要成為 “Leader”沒那么簡單,得有兩把刷子。需要滿足以下條件:
獲得其他哨兵基友過半的贊成票;
贊成票的數(shù)量還要大于等于配置文件的 quorum 的值。
如果哨兵集群有 2 個實例,此時,一個哨兵要想成為 Leader,必須獲得 2 票,而不是 1 票。所以,如果有個哨兵掛掉了,那么,此時的集群是無法進(jìn)行主從庫切換的。因此,通常我們至少會配置 3 個哨兵實例。
這也是為啥哨兵集群部署成單數(shù)的原因,雙數(shù)的話多余浪費。
選舉流程如下圖所示:
Redis哨兵執(zhí)行主從切換通過 pub/sub 實現(xiàn)客戶端事件通知
“
65 哥:新 master 選出來了,要怎么公示天下呢?
”
當(dāng)然是召開新聞發(fā)布會呀,邀請消息相關(guān)類型的媒體報道傳播,感興趣的人自然就去關(guān)注訂閱相關(guān)事件,并根據(jù)事件做出行動。
在 Redis 也是類似,通過 pub/sub 機(jī)制發(fā)布不同事件,讓客戶端在這里訂閱消息。客戶端可以訂閱哨兵的消息,哨兵提供的消息訂閱頻道有很多,不同頻道包含了主從庫切換過程中的不同關(guān)鍵事件。
也就是在不同的“微信群”發(fā)布不同的事件,讓對該事件感興趣的人進(jìn)群即可。
master 下線事件
+sdown:進(jìn)入“主觀下線”狀態(tài);
-sdown:退出“主觀下線”狀態(tài);
+odown:進(jìn)入“客觀下線”狀態(tài);
-odown:退出“客觀下線”狀態(tài);
slave 重新配置事件
+slave-reconf-sent:哨兵發(fā)送 replicaof 命令重新配置從庫;
+slave-reconf-inprog:slave 配置了新 master,但是尚未進(jìn)行同步;
+slave-reconf-done:slave 配置了新 master,并與新 master 完成了數(shù)據(jù)同步;
新主庫切換
+switch-master:master 地址發(fā)生了變化。
知道了這些頻道之后,就可以讓客戶端從哨兵這里訂閱消息了。客戶端讀取哨兵的配置文件后,可以獲得哨兵的地址和端口,和哨兵建立網(wǎng)絡(luò)連接。
然后,我們可以在客戶端執(zhí)行訂閱命令,來獲取不同的事件消息。
舉個栗子:如下指令訂閱“所有實例進(jìn)入客觀下線狀態(tài)的事件”
SUBSCRIBE?+odown?
?
注意事項與配置說明
發(fā)現(xiàn)了沒,Redis 的 pub/sub 發(fā)布訂閱機(jī)制尤其重要,有了 pub/sub 機(jī)制,哨兵和哨兵之間、哨兵和從庫之間、哨兵和客戶端之間就都能建立起連接了,各種事件的發(fā)布也是通過這個機(jī)制實現(xiàn)。
down-after-milliseconds
Sentinel 配置文件中的 down-after-milliseconds 選項指定了 Sentinel 判斷實例進(jìn)入主觀下線所需的時間長度:如果一個實例在 down-after-milliseconds 毫秒內(nèi),連續(xù)向 Sentinel 返回?zé)o效回復(fù),那么 Sentinel 會修改這個實例所對應(yīng)數(shù)據(jù),以此來表示這個實例已經(jīng)進(jìn)入主觀下線狀態(tài)。
要保證所有哨兵實例的配置是一致的,尤其是主觀下線的判斷值 down-after-milliseconds。因為這個值在不同的哨兵實例上配置不一致,導(dǎo)致哨兵集群一直沒有對有故障的主庫形成共識,也就沒有及時切換主庫,最終的結(jié)果就是集群服務(wù)不穩(wěn)定。
down-after-milliseconds * 10
down-after-milliseconds 是我們認(rèn)定主從庫斷連的最大連接超時時間。如果在 down-after-milliseconds 毫秒內(nèi),主從節(jié)點都沒有通過網(wǎng)絡(luò)聯(lián)系上,我們就可以認(rèn)為主從節(jié)點斷連了。如果發(fā)生斷連的次數(shù)超過了 10 次,就說明這個從庫的網(wǎng)絡(luò)狀況不好,不適合作為新主庫。
?
總結(jié)
哨兵主要任務(wù)
Redis 哨兵機(jī)制是實現(xiàn) Redis 不間斷服務(wù)的高可用手段之一。主從架構(gòu)集群的數(shù)據(jù)同步,是數(shù)據(jù)可靠的基礎(chǔ)保障;主庫宕機(jī),自動執(zhí)行主從切換是服務(wù)不間斷的關(guān)鍵支撐。
Redis 哨兵機(jī)制實現(xiàn)了主從庫的自動切換,再也不怕跟女盆友么么噠的時候 master 宕機(jī)了:
監(jiān)控 master 與 slave 運行狀態(tài),判斷是否客觀下線;
master 客觀下線后,選擇一個 slave 切換成 master;
通知 slave 和客戶端新 master 信息。
哨兵集群原理
為了避免單個哨兵故障后無法進(jìn)行主從切換,以及為了減少誤判率,又引入了哨兵集群;哨兵集群又需要有一些機(jī)制來支撐它的正常運行:
基于 pub/sub 機(jī)制實現(xiàn)哨兵集群之間的通信;
基于 INFO 命令獲取 slave 列表,幫助 哨兵與 slave 建立連接;
通過哨兵的 pub/sub,實現(xiàn)了與客戶端和哨兵之間的事件通知。
主從切換,并不是隨意選擇一個哨兵就可以執(zhí)行,而是通過投票仲裁,選擇一個 Leader,由這個 Leader 負(fù)責(zé)主從切換。
參考資料
[1][redis設(shè)計與實現(xiàn)] 黃健宏
[2][redis核心技術(shù)與實戰(zhàn)] https://time.geekbang.org/column/article/274483
[3][redis 深度歷險:核心原理與應(yīng)用實戰(zhàn)] https://juejin.cn/book/6844733724618129422/p/6844733724722987021
[4][redis 專題:深入解讀哨兵模式] https://juejin.cn/post/6934984432273063967#heading-0
[5][redis哨兵原理,我忍你很久了!] https://www.modb.pro/db/25926
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ArrayList和LinkedList
- 下一篇: 反汇编 bvi