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 進程協(xié)同工作,組成了一套分布式的架構(gòu),它負責(zé)持續(xù)監(jiān)控主從節(jié)點的健康狀況,當主節(jié)點掛掉時,自動選擇一個最優(yōu)的從節(jié)點切換為主節(jié)點??蛻舳藖磉B接集群時,會首先連接 sentinel,通過 sentinel 來查詢主節(jié)點的地址,然后再去連接主節(jié)點進行數(shù)據(jù)交互。當主節(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é)點的故障達成一致,在 3 個 sentinel 節(jié)點中選擇一個作為 leader ,例如,選舉出 sentinel-0 節(jié)點作為 leader,來負責(zé)故障轉(zhuǎn)移。

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

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

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