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