Redis Sentinel 架構(gòu)原理詳解
每日英文
You think letting go would make me happy, but you don’t know my biggest happiness is to hold your hand.
你以為放手可以成全我的幸福,可你不知道,我最大的幸福就是能和你手牽手。
每日掏心話
牽念于心守一方寧靜品一份孤獨(dú),于漫漫紅塵中攜一縷安然,淡泊煙火人間。
來自:CoderJed?|?責(zé)編:樂樂
鏈接:jianshu.com/p/00a6d7cc82b2

程序員小樂(ID:study_tech)第 736?次推文? ?圖片來自網(wǎng)絡(luò)
往日回顧:傳言阿里P10趙海平,被P11多隆判定3.25離職,如何評價阿里 P10 趙海平對王垠的面試?
? ?正文? ?
1. Redis Sentinel 簡介
redis 的主從復(fù)制模式下,一旦主節(jié)點由于故障不能提供服務(wù),需要人工將從節(jié)點晉升為主節(jié)點,再通知所有的程序把 master 地址統(tǒng)統(tǒng)改一遍,然后重新上線。毫無疑問,這種故障處理的方法是效率低下的,無法接受。
于是,redis 從 2.8 開始正式提供了 sentinel 架構(gòu)來解決這個問題。
redis sentinel 是 redis 的高可用實現(xiàn)方案,多個 sentinel 進(jìn)程協(xié)同工作,組成了一套分布式的架構(gòu),它負(fù)責(zé)持續(xù)監(jiān)控主從節(jié)點的健康狀況,當(dāng)主節(jié)點掛掉時,自動選擇一個最優(yōu)的從節(jié)點切換為主節(jié)點。客戶端來連接集群時,會首先連接 sentinel,通過 sentinel 來查詢主節(jié)點的地址,然后再去連接主節(jié)點進(jìn)行數(shù)據(jù)交互。當(dāng)主節(jié)點發(fā)生故障時,客戶端會重新向 sentinel 要地址,sentinel 會將最新的主節(jié)點地址告訴客戶端。如此應(yīng)用程序?qū)o需重啟即可自動完成節(jié)點切換。
2. Redis Sentinel 架構(gòu)及原理
我們以經(jīng)典的一主二從架構(gòu)來說明的 sentinel 的原理。
(1) 主從切換的過程
每個 sentinel 節(jié)點通過定期監(jiān)控 master 的健康狀況。

主節(jié)點出現(xiàn)故障,兩個從節(jié)點與主節(jié)點失去連接,主從復(fù)制失敗。

sentinel 集群 發(fā)現(xiàn) master 故障后,多個 sentinel 節(jié)點對主節(jié)點的故障達(dá)成一致,在 3 個 sentinel 節(jié)點中選擇一個作為 leader ,例如,選舉出 sentinel-0 節(jié)點作為 leader,來負(fù)責(zé)故障轉(zhuǎn)移。

leader sentinel 把一個 slave 節(jié)點提升為 master,并讓另一個 slave 從新的 master 復(fù)制數(shù)據(jù),并告知客戶端新的 master 的信息。

故障的舊 master 上線后,leader sentinel 讓它從新的 master 復(fù)制數(shù)據(jù)。

以上就是 sentinel 集群進(jìn)行故障轉(zhuǎn)移的整體流程,具體的一些細(xì)節(jié)還會詳細(xì)介紹,這里先總結(jié)一下 sentinel 集群在 redis 主從架構(gòu)高可用中起到的 4 個作用:
集群監(jiān)控
sentinel 節(jié)點會定期檢測 redis 數(shù)據(jù)節(jié)點、其余 sentinel 節(jié)點是否故障。故障轉(zhuǎn)移
實現(xiàn)從節(jié)點晉升為主節(jié)點并維護(hù)后續(xù)正確的主從關(guān)系。配置中心
sentinel 架構(gòu)中,客戶端在初始化的時候連接的是 sentinel 集群,從中獲取主節(jié)點信息。消息通知
sentinel 節(jié)點會將故障轉(zhuǎn)移的結(jié)果通知給客戶端。
此外,使用 sentinel 集群而不是單個 sentinel 節(jié)點去監(jiān)控 redis 主從架構(gòu)有兩個好處:
對于節(jié)點的故障判斷由多個 sentinel 節(jié)點共同完成,這樣可以有效地防止誤判。
sentinel 集群可以保證自身的高可用性,即某個 sentinel 節(jié)點自身故障也不會影響 sentinel 集群的健壯性。
(2) sentinel 集群的監(jiān)控功能詳解
sentinel 集群通過三個定時監(jiān)控任務(wù)完成對各個節(jié)點發(fā)現(xiàn)和監(jiān)控。
每隔10秒,每個 sentinel 節(jié)點會向主節(jié)點和從節(jié)點發(fā)送 info 命令獲取 redis 主從架構(gòu)的最新情況。例如,發(fā)送
info replication命令可以得到以下信息:
node01:6379>?info?replication
#?Replication
role:master
connected_slaves:2
slave0:ip=192.168.239.102,port=6379,state=online,offset=18621889,lag=1
slave1:ip=192.168.239.103,port=6379,state=online,offset=18621889,lag=1
這樣,sentinel 集群就可以得知 master 和 slave 的基本信息,通過向主節(jié)點執(zhí)行 info 命令,獲取從節(jié)點的信息,所以 sentinel 節(jié)點不需要顯式配置監(jiān)控從節(jié)點,當(dāng)有新的從節(jié)點加入時都可以立刻感知出來,當(dāng) master 節(jié)點故障或者故障轉(zhuǎn)移后,可以通過 info 命令實時更新 redis 主從信息。
每隔2秒,每個 sentinel 節(jié)點會向 redis 數(shù)據(jù)節(jié)點的
__sentinel__:hello這個channel(頻道)發(fā)送一條消息,消息的內(nèi)容是:
? ? ? ? ? ? ?
每個 sentinel 節(jié)點會訂閱該 channel,來了解其他
sentinel節(jié)點以及它們對主節(jié)點的判斷,所以這個定時任務(wù)可以完成以下兩個工作:
發(fā)現(xiàn)新的 sentinel節(jié)點:通過訂閱主節(jié)點的
__sentinel__:hello了解其他的 sentinel 節(jié)點信息,如果是新加入的 sentinel 節(jié)點,將該 sentinel 節(jié)點信息保存起來,并與該 sentinel 節(jié)點創(chuàng)建連接sentinel 節(jié)點之間交換主節(jié)點的狀態(tài),用于確認(rèn) master 下線和故障處理的 leader 選舉。
每隔1秒,每個 sentinel 節(jié)點會向主節(jié)點、從節(jié)點、其余 sentinel 節(jié)點發(fā)送一條ping命令做一次心跳檢測,來確認(rèn)這些節(jié)點是否可達(dá)。通過定時發(fā)送ping命令,sentinel 節(jié)點對主節(jié)點、從節(jié)點、其余 sentinel 節(jié)點都建立起連接,實現(xiàn)了對每個節(jié)點的監(jiān)控,這個定時任務(wù)是節(jié)點下線判定的重要依據(jù)。
(3) sdown(主觀下線) 和 odown(客觀下線)
主觀下線
每個 sentinel 節(jié)點每隔1秒對主節(jié)
點、從節(jié)點、其他 sentinel 節(jié)點發(fā)送 ping 命令做心跳檢測,當(dāng)這些節(jié)點超過down-after-milliseconds沒有進(jìn)行有效回復(fù),sentinel節(jié)點就會認(rèn)為該節(jié)點下線,這個行為叫做主觀下線。主觀下線是某個 sentinel 節(jié)點的判斷,并不是 sentinel 集群的判斷,所以存在誤判的可能。客觀下線
當(dāng) sentinel 主觀下線的節(jié)點是主節(jié)點時,該 sentinel 節(jié)點會通過sentinel ismaster-down-by-addr命令向其他 sentinel 節(jié)點詢問對主節(jié)點的判斷,當(dāng)超過個數(shù)(quorum可配置)的 sentinel 節(jié)點認(rèn)為主節(jié)點確實有問題,這時該 sentinel 節(jié)點會做出客觀下線的決定,這樣客觀下線的含義是比較明顯了,也就是大部分是 sentinel 節(jié)點都對主節(jié)點的下線做了同意的判定,那么這個判定就是客觀的。
介紹一下sentinel is-master-down-by-addr命令:
sentinel?is-master-down-by-addr?<ip>?<port>?<current_epoch>?<runid>
ip、port:詢問此 ip:port 的 redis 進(jìn)程是否下線
current_epoch:當(dāng)前配置版本
runid:如果為當(dāng)前 sentinel 節(jié)點的 runid,則此命令用于申請自己成為故障處理的 leader,如果是*,則此命令用于向其他 sentinel 節(jié)點確認(rèn) master 是否下線。
此命令返回結(jié)果包括3個信息:
down_state:目標(biāo) sentinel 節(jié)點對于主節(jié)點的下線判斷,1是下線,0是在線。
leader_runid:當(dāng)leader_runid等于
*時,代表返回結(jié)果是說明主節(jié)點是否不可達(dá),當(dāng) leader_runid 等于具體的runid,代表目標(biāo)節(jié)點同意該 runid sentinel 節(jié)點成為 leader。leader_epoch:leader 版本。
(4) 故障轉(zhuǎn)移前的 leader 選舉
當(dāng) sentinel 集群確認(rèn) master odown,需要選舉出一個 leader 節(jié)點來進(jìn)行故障轉(zhuǎn)移,選舉過程如下:
每個在線的 sentinel 節(jié)點都有資格成為 leader,當(dāng)它確認(rèn)主節(jié)點客觀下線時候,會向其他 sentinel 節(jié)點發(fā)送
sentinel is-master-down-by-addr命令,要求將自己設(shè)置為leader,比如 sentinel-0 節(jié)點首先發(fā)起請求成為 leader 的請求。每個 sentinel 節(jié)點都只能投出一票,于是當(dāng) sentinel-0 節(jié)點發(fā)起成為 leader 的請求后,會得到 sentinel-1 和 sentinel-2 節(jié)點的投票,總共得到 2 票,得到的票數(shù)和以下公式計算的值作比較:
??max(quorum,?num(sentinels)?/?2?+?1)
=?max(2,?3?/?2?+?1)?
=?max(2,?1?+?1)?
=?max(2,?2)
=?2
當(dāng)?shù)玫降钠睌?shù) >= max(quorum, num(sentinels) / 2 + 1) 的值,那么該 sentinel 節(jié)點成為 leader,于是,sentinel-0 節(jié)點成為 leader。
比如下一個確認(rèn) master 客觀下線的 sentinel 節(jié)點為 sentinel-1,當(dāng)它發(fā)起成為 leader 的請求后,由于 sentinel-2 節(jié)點已經(jīng)給 sentinel-0 節(jié)點投過票了,于是它只能得到 sentinel-0 節(jié)點投的一票,所以它不能成為 leader,而當(dāng) sentinel-2 發(fā)起請求成為 leader 的請求后,它一票都得不到。于是當(dāng)已經(jīng)選舉出 leader 后,就不會再繼續(xù)進(jìn)行選舉流程了,因為是沒有意義的。
如果一次選舉沒有選舉出 leader,那么會進(jìn)行下一次選舉。
總結(jié):正常情況下,哪個 sentinel 節(jié)點最先確認(rèn) master 客觀下線,哪個 sentinel 節(jié)點就會成為執(zhí)行故障轉(zhuǎn)移的 leader。
(5) 故障轉(zhuǎn)移前新的 master 選擇
要執(zhí)行故障轉(zhuǎn)移,首先要從 slave 中選擇一個作為新的 master,選擇的準(zhǔn)則如下:
不選擇不健康的 slave,以下狀態(tài)的 slave 是不健康的:
主觀下線的 slave
大于等于5秒沒有回復(fù)過 sentinel 節(jié)點 ping 響應(yīng)的 slave
與 master 失聯(lián)超過
down-after-milliseconds * 10秒的 slave
對健康的 slave 進(jìn)行排序
選擇 priority(從節(jié)點優(yōu)先級,可配置,默認(rèn)100)最低的從節(jié)點,如果有優(yōu)先級相同的節(jié)點,進(jìn)行下一步。注意如果這個值配置為0,則代表禁止該節(jié)點成為 master。
選擇復(fù)制偏移量最大的從節(jié)點(復(fù)制的最完整),如果有復(fù)制偏移量相等的節(jié)點,進(jìn)行下一步。
選擇 runid 最小的從節(jié)點。
然后就是 leader 進(jìn)行故障轉(zhuǎn)移的過程了:
leader 對選擇出來的要成為 new master 的 slave 執(zhí)行
slaveof no one命令讓其成為 new master。leader 會向剩余的 slave 發(fā)送命令,讓它們成為 new master 的 slave。
leader 會將 old master 更新為 slave點,并保持著對其關(guān)注,當(dāng)其恢復(fù)后命令它去復(fù)制 new master。復(fù)制規(guī)則和
parallel-syncs配置有關(guān)。該配置指定了在執(zhí)行故障轉(zhuǎn)移時,最多可以有多少個 slave 同時對 new master 進(jìn)行同步,這個數(shù)字越小,完成故障轉(zhuǎn)移所需的時間就越長。如果從服務(wù)器被設(shè)置為允許使用過期數(shù)據(jù)集(redis.conf 中slave-serve-stale-data配置) ,那么你可能不希望所有 slave 都在同一時間向 new master 發(fā)送同步請求,因為盡管復(fù)制過程的絕大部分步驟都不會阻塞slave, 但 slave 在 load new master 發(fā)來的 RDB 文件時, 仍然會造成其在一段時間內(nèi)不能處理請求。如果全部 slave 一起對 new master 進(jìn)行同步, 那么就可能會造成所有 slave 在短時間內(nèi)全部不可用的情況出現(xiàn)。你可以通過將這個值設(shè)為 1 來保證故障轉(zhuǎn)移后最多只有一個 slave 處于不可用狀態(tài)。但這樣的話,全部 slave 的數(shù)據(jù)同步就是串行的,這樣就會增加故障轉(zhuǎn)移整個過程的時間。
(6) Sentinel 集群的 quorum ?和 majority
quorum 是在 sentinel.conf中手動配置的,默認(rèn)為2
#?sentinel?monitor?[master-name]?[master-ip]?[master-port]?[quorum]?
sentinel?monitor?mymaster?127.0.0.1?6379?2
意味著,只有 大于等于 quorum 數(shù)量都認(rèn)為 master 主觀下線,sentinel 集群才會認(rèn)為 master 客觀下線。
sentinel 集群執(zhí)行故障轉(zhuǎn)移時需要選舉 leader,此時涉及到 majority,majority 代表 sentinel 集群中大部分 sentinel 節(jié)點的個數(shù),只有大于等于
max(quorum, majority)個節(jié)點給某個 sentinel 節(jié)點投票,才能確定該 sentinel 節(jié)點為 leader,majority 的計算方式為:num(sentinels) / 2 + 1,比如:
2 個節(jié)點的 sentinel 集群的 majority為 2
3 個節(jié)點的 sentinel 集群的 majority為 2
4 個節(jié)點的 sentinel 集群的 majority為 3
5 個節(jié)點的 sentinel 集群的 majority為 3
所以 sentinel 集群的節(jié)點個數(shù)至少為3個,當(dāng)節(jié)點數(shù)為2時,假如一個 sentinel 節(jié)點宕機(jī),那么剩余一個節(jié)點是無法讓自己成為 leader 的,因為2個節(jié)點的 sentinel 集群的 majority 是 2,此時沒有2個節(jié)點都給剩余的節(jié)點投票,也就無法選擇出 leader,從而無法進(jìn)行故障轉(zhuǎn)移。
另外最好把 quorum 的值設(shè)置為 <= majority,否則即使 sentinel 集群剩余的節(jié)點滿足 majority 數(shù),但是有可能不能滿足 quorum 數(shù),那還是無法選舉 leader,也就不能進(jìn)行故障轉(zhuǎn)移。
(7) configuration epoch
configuration epoch 是當(dāng)前 redis 主從架構(gòu)的配置版本號,無論是 sentinel 集群選舉 leader 還是進(jìn)行故障轉(zhuǎn)移的時候,要求各 sentinel 節(jié)點得到的 configuration epoch 都是相同的,sentinel is-master-down-by-addr 命令中就必須有當(dāng)前配置版本號這個參數(shù),在選舉 leader 過程中,如果本次選舉失敗,那么進(jìn)行下一次選舉,就會更新配置版本號,也就是說,每次選舉都對應(yīng)一個新的 configuration epoch,在故障轉(zhuǎn)移的過程中,也要求各個 sentinel 節(jié)點使用相同的 configuration epoch。
在故障轉(zhuǎn)移成功之后,sentinel leader 會更新生成最新的 master 配置,configuration epoch 也會更新,然后同步給其他的 sentinel 節(jié)點,這樣保證 sentinel 集群中保存的 master <-> slave 配置都是最新的,當(dāng) client 請求的時候就會拿到最新的配置信息。
(8) Redis Sentinel 可能出現(xiàn)的問題以及解決辦法
redis sentinel 無法保證數(shù)據(jù)完全不丟失,原因有兩個:
(1) 異步復(fù)制導(dǎo)致的數(shù)據(jù)丟失
因為 master -> slave 的復(fù)制是異步的,所以可能有部分?jǐn)?shù)據(jù)還沒復(fù)制到 slave,master 就宕機(jī)了,此時這部分?jǐn)?shù)據(jù)就丟失了。
(2) redis 服務(wù)腦裂導(dǎo)致的數(shù)據(jù)丟失
腦裂,也就是說,某個 master 所在機(jī)器突然網(wǎng)絡(luò)故障,跟其他 slave 機(jī)器不能連接,但是實際上 master 還運(yùn)行著。此時哨兵可能就會認(rèn)為 master 宕機(jī)了,然后開啟選舉,將其他 slave 切換成了master,這個時候,集群里就會有兩個master,也就是所謂的腦裂。此時雖然某個 slave 被切換成了 master,但是 client 還沒來得及切換到新的master,還繼續(xù)寫向舊 master 的數(shù)據(jù)就丟失了。因為舊 master 再次恢復(fù)的時候,會被作為一個 slave 掛到新的 master 上去,自己的數(shù)據(jù)會清空,重新從新的 master 復(fù)制數(shù)據(jù)。
redis 提供了兩個配置參數(shù)可以盡量丟失少的數(shù)據(jù):
min-slaves-to-write?1
min-slaves-max-lag?10
第一個參數(shù)表示 master 必須至少有一個 slave 在進(jìn)行正常復(fù)制,否則就拒絕寫請求,此時 master 喪失可用性。
何為正常復(fù)制,何為異常復(fù)制?這個就是由第二個參數(shù)控制的,它的單位是秒,
表示如果 10s 沒有收到從節(jié)點的反饋,就意味著從節(jié)點同步不正常。
這樣可以把 master 宕機(jī)期間的數(shù)據(jù)丟失降低到可控范圍內(nèi)。
redis-2.6 版本提供的是 redis sentinel v1版本,但是功能性和健壯性都有一些問題,如果想使用 redis sentinel的話,建議使用2.8以上版本,也就是v2版本的 redis sentinel。

歡迎在留言區(qū)留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發(fā),學(xué)習(xí)能力的提升上有新的認(rèn)識,歡迎轉(zhuǎn)發(fā)分享給更多人。
歡迎各位讀者加入程序員小樂技術(shù)群,在公眾號后臺回復(fù)“加群”或者“學(xué)習(xí)”即可。
猜你還想看
面試官問:平常你是怎么對Java服務(wù)進(jìn)行調(diào)優(yōu)的?
關(guān)注「程序員小樂」,收看更多精彩內(nèi)容

