申明
本文章首發自本人公眾號:壹枝花算不算浪漫,如若轉載請標明來源!
感興趣的小伙伴可關注個人公眾號:壹枝花算不算浪漫
22.jpg
前言
Redis 的 Sentinel 系統用於管理多個 Redis 服務器(instance), 該系統執行以下三個任務:
- 監控(Monitoring): Sentinel 會不斷地檢查你的主服務器和從服務器是否運作正常。
- 提醒(Notification): 當被監控的某個 Redis 服務器出現問題時, Sentinel 可以通過 API 向管理員或者其他應用程序發送通知。
- 自動故障遷移(Automatic failover): 當一個主服務器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主服務器的其中一個從服務器升級為新的主服務器, 並讓失效主服務器的其他從服務器改為復制新的主服務器; 當客戶端試圖連接失效的主服務器時, 集群也會向客戶端返回新主服務器的地址, 使得集群可以使用新主服務器代替失效服務器。
Redis Sentinel 是一個分布式系統, 你可以在一個架構中運行多個 Sentinel 進程(progress), 這些進程使用流言協議(gossip protocols)來接收關於主服務器是否下線的信息, 並使用投票協議(agreement protocols)來決定是否執行自動故障遷移, 以及選擇哪個從服務器作為新的主服務器。
什么是高可用?
架構上,我們講高可用,一般說系統99%、99.9%、99.99% 等更多情形
例如一年365天 * 99.99% = 365.9天是可以對外提供服務的,換算下來是可以宕機
3153.6s,換成52.56min,0.876h
一般系統不可用可以分為以下幾種情況:
- 機器宕機了
- JVM進程OOM了
- 機器cpu 100%
- 磁盤滿了,系統各種IO報錯
- 其他
例如Redis,我們使用主從架構,是單個master多個slave的,如果master宕機了該如何處理?此時Redis就無法寫入了,如果我們使用哨兵模式,是會自動將某個slave提升為master的,繼續對外提供服務。
哨兵模式
sentinal:哨兵,它是redis集群中非常重要的一個組件,主要功能如下:
- 集群監控,負責監控redis master和slave進程是否正常工作
- 消息通知,如果某個redis實例有故障,那么哨兵負責發送消息作為報警通知給管理員
- 故障轉移:如果master node掛掉了,會自動轉移給slave node上
- 配置中心,如果故障轉移發生了,通知client客戶端新的master地址
哨兵本身也是分布式的,作為一個哨兵集群去運行,互相協同工作
- 故障轉移時,判斷一個master node是宕機了,需要大部分哨兵都同意才行,涉及了分布式選舉問題
- 即使部分哨兵節點掛掉了,哨兵集群還是能正常工作的
哨兵配置
Redis 源碼中包含了一個名為 sentinel.conf 的文件, 這個文件是一個帶有詳細注釋的 Sentinel 配置文件示例。
運行一個 Sentinel 所需的最少配置如下所示:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
第一行配置指示 Sentinel 去監視一個名為 mymaster 的主服務器, 這個主服務器的 IP 地址為 127.0.0.1 , 端口號為 6379 , 而將這個主服務器判斷為失效至少需要 2(quorum) 個 Sentinel 同意 (只要同意 Sentinel 的數量不達標,自動故障遷移就不會執行)。
不過要注意, 無論你設置要多少個 Sentinel 同意才能判斷一個服務器失效, 一個 Sentinel 都需要獲得系統中多數(majority) Sentinel 的支持, 才能發起一次自動故障遷移, 並預留一個給定的配置紀元 (configuration Epoch ,一個配置紀元就是一個新主服務器配置的版本號)。
換句話說, 在只有少數(minority) Sentinel 進程正常運作的情況下, Sentinel 是不能執行自動故障遷移的。
哨兵模式運行機制
image.png
這個是經典的3節點哨兵集群,我們配置quorum = 2,3個節點的majority也是2,如果M1所在的機器宕機了,那么三個哨兵還剩下2個,S2和S3可以一致認為mater宕機,然后選舉一個來執行故障轉移
同時majority也是2,這個2個哨兵可以允許執行故障轉移的。
這里再說下,如果是兩節點哨兵,類似:
image.png
配置的quorum = 1,master宕機,s1和s2中只要有一個哨兵認為master宕機就可以進行切換,同時s1和s2中選舉一個哨兵來執行故障轉移
但是此時majority=2,至少有2個或者的哨兵才能夠允許執行故障轉移,此時只有R1,故障是不會允許轉移的
允許故障轉移的條件時:或者的sentinal節點數>=majority
這里再說兩個概念:
- quorum:sentinal集群中會配置該參數,例如5台機器,配置的quorum為3,那么如果有3個節點認為master宕機了,sentinal集群就會認為master宕機了,每個節點認為宕機被稱為主觀宕機sdown,sentinal集群認為master宕機被稱為客觀宕機odown
- majority 代表大多數的意思,例如2的majority=2,3的majority=2,4的majority=2,5的majority=3
哨兵模式下主備切換數據丟失問題
主備切換過程中,有兩種情形會導致數據丟失:
-
異步復制導致的數據丟失
master->slave的復制時異步的,所以可能有部分數據還沒有復制到slave時,master宕機了,此時這部分數據就丟失了 -
腦裂導致的數據丟失
腦裂就是說某個master所在的機器脫離了正常網絡,跟其他slave機器不能連接,但是實際上master還運行着,此時哨兵可能就會認為master宕機了,然后開啟選舉,將其他slave切換成了master。這個時候集群中就會有兩個master,也就是所謂的腦裂
此時雖然某個slave被切換成了master,但是可能client還沒來得及切換到新的master,還繼續向舊的master寫數據,這部分數據可能會丟失
因此舊的master再次恢復的時候,會被作為一個slave掛到新的master上去,自己的數據會被清空,重新從新的master復制數據
03_Redis哨兵模式下腦裂問題.jpg
哨兵模式數據丟失問題解決方案
從 Redis 2.8 開始, 為了保證數據的安全性, 可以通過配置, 讓主服務器只在有至少 N 個當前已連接從服務器的情況下, 才執行寫命令。
不過, 因為 Redis 使用異步復制, 所以主服務器發送的寫數據並不一定會被從服務器接收到, 因此, 數據丟失的可能性仍然是存在的。
以下是這個特性的運作原理:
-
從服務器以每秒一次的頻率 PING 主服務器一次, 並報告復制流的處理情況。
-
主服務器會記錄各個從服務器最后一次向它發送 PING 的時間。
-
用戶可以通過配置, 指定網絡延遲的最大值
min-slaves-max-lag
, 以及執行寫操作所需的至少從服務器數量min-slaves-to-write
。
如果至少有 min-slaves-to-write
個從服務器, 並且這些服務器的延遲值都少於 min-slaves-max-lag
秒, 那么主服務器就會執行客戶端請求的寫操作。
你可以將這個特性看作 CAP 理論中的 C 的條件放寬版本: 盡管不能保證寫操作的持久性, 但起碼丟失數據的窗口會被嚴格限制在指定的秒數中。
另一方面, 如果條件達不到 min-slaves-to-write
和 min-slaves-max-lag
所指定的條件, 那么寫操作就不會被執行, 主服務器會向請求執行寫操作的客戶端返回一個錯誤。
以下是這個特性的兩個選項和它們所需的參數:
-min-slaves-to-write <number of slaves>
min-slaves-max-lag
詳細的信息可以參考 Redis 源碼中附帶的 redis.conf
示例文件。
上面兩個配置可以減少異步復制和腦裂導致的數據丟失
-
減少異步復制的數據丟失
有了min-slaves-max-lag
這個配置,就可以確保說,一旦slave復制數據和ack延時太長,就認為可能master宕機后損失的數據太多了,那么就拒絕寫請求,這樣可以把master宕機時由於部分數據未同步到slave導致的數據丟失降低的可控范圍內 -
減少腦裂的數據丟失
如果一個master出現了腦裂,跟其他slave丟了連接,那么上面兩個配置可以確保說,如果不能繼續給指定數量的slave發送數據,而且slave超過10秒沒有給自己ack消息,那么就直接拒絕客戶端的寫請求
這樣腦裂后的舊master就不會接受client的新數據,也就避免了數據丟失
上面的配置就確保了,如果跟任何一個slave丟了連接,在10秒后發現沒有slave給自己ack,那么就拒絕新的寫請求
因此在腦裂場景下,最多就丟失10秒的數據。
哨兵模式其他一些機制原理分析
sdown和odown轉換機制
sdown(subjectively down)和odown(objectively down)是年終失敗狀態:
- sdown是主觀宕機,一個哨兵如果覺得master宕機了,那么就是主觀宕機
- odown是客觀宕機,如果quorun數量的哨兵都覺得一個master宕機了,那么就是客觀宕機
sdown達成的條件很簡單,如果一個哨兵ping一個master,超過了is-master-down-after-milliseconds指定的毫秒數之后,就主觀認為master宕機了
例如我們可以配置:
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
這里就代表如果ping master超過了5s鍾就認為master sdown
哨兵集群的自動發現機制
一個 Sentinel 可以與其他多個 Sentinel 進行連接, 各個 Sentinel 之間可以互相檢查對方的可用性, 並進行信息交換。
你無須為運行的每個 Sentinel 分別設置其他 Sentinel 的地址, 因為 Sentinel 可以通過發布與訂閱功能來自動發現正在監視相同主服務器的其他 Sentinel , 這一功能是通過向頻道__sentinel__:hello
發送信息來實現的。
與此類似, 你也不必手動列出主服務器屬下的所有從服務器, 因為 Sentinel 可以通過詢問主服務器來獲得所有從服務器的信息。
-
每個 Sentinel 會以每兩秒一次的頻率, 通過發布與訂閱功能, 向被它監視的所有主服務器和從服務器的
__sentinel__:hello
頻道發送一條信息, 信息中包含了 Sentinel 的 IP 地址、端口號和運行 ID (runid)。 -
每個 Sentinel 都訂閱了被它監視的所有主服務器和從服務器的
__sentinel__:hello
頻道, 查找之前未出現過的 sentinel (looking for unknown sentinels)。 當一個 Sentinel 發現一個新的 Sentinel 時, 它會將新的 Sentinel 添加到一個列表中, 這個列表保存了 Sentinel 已知的, 監視同一個主服務器的所有其他 Sentinel 。 -
Sentinel 發送的信息中還包括完整的主服務器當前配置(configuration)。 如果一個 Sentinel 包含的主服務器配置比另一個 Sentinel 發送的配置要舊, 那么這個 Sentinel 會立即升級到新配置上。
-
在將一個新 Sentinel 添加到監視主服務器的列表上面之前, Sentinel 會先檢查列表中是否已經包含了和要添加的 Sentinel 擁有相同運行 ID 或者相同地址(包括 IP 地址和端口號)的 Sentinel , 如果是的話, Sentinel 會先移除列表中已有的那些擁有相同運行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 。
slave配置的自動糾正
哨兵會負責自動糾正slave的一些配置,比如slave如果要成為潛在的master候選人,哨兵會確保slave在復制現有master的數據; 如果slave連接到了一個錯誤的master上,比如故障轉移之后,那么哨兵會確保它們連接到正確的master上
slave->master選舉算法
如果一個master被認為odown了,而且majority哨兵都允許了主備切換,那么某個哨兵就會執行主備切換操作,此時首先要選舉一個slave來
會考慮slave的一些信息:
- 跟master斷開連接的時長
- slave優先級
- 復制offset
- run id
如果一個slave跟master斷開連接已經超過了down-after-milliseconds的10倍,外加master宕機的時長,那么slave就被認為不適合選舉為master
own-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下來會對slave進行排序:
- slave優先級進行排序,slave priority越低,優先級就越高
- 如果slave priority相同,那么看replica offset,哪個slave復制了越多的數據,offset越靠后,優先級就越高
- 如果上面兩個條件都相同,那么選擇一個run id比較小的那個slave
quorum和majority
每次一個哨兵要做主備切換,首先需要quorum數量的哨兵認為odown,然后選舉出一個哨兵來做切換,這個哨兵還得得到majority哨兵的授權,才能正式執行切換
如果quorum < majority,比如5個哨兵,majority就是3,quorum設置為2,那么就3個哨兵授權就可以執行切換
但是如果quorum >= majority,那么必須quorum數量的哨兵都授權,比如5個哨兵,quorum是5,那么必須5個哨兵都同意授權,才能執行切換
configuration epoch
哨兵會對一套redis master+slave進行監控,有相應的監控的配置
執行切換的那個哨兵,會從要切換到的新master(salve->master)那里得到一個configuration epoch,這就是一個version號,每次切換的version號都必須是唯一的
如果第一個選舉出的哨兵切換失敗了,那么其他哨兵,會等待failover-timeout時間,然后接替繼續執行切換,此時會重新獲取一個新的configuration epoch,作為新的version號
configuraiton傳播
哨兵完成切換之后,會在自己本地更新生成最新的master配置,然后同步給其他的哨兵,就是通過之前說的pub/sub消息機制
這里之前的version號就很重要了,因為各種消息都是通過一個channel去發布和監聽的,所以一個哨兵完成一次新的切換之后,新的master配置是跟着新的version號的
其他的哨兵都是根據版本號的大小來更新自己的master配置的
故障轉移
一次故障轉移操作由以下步驟組成:
- 發現主服務器已經進入客觀下線狀態。
- 對我們的當前紀元進行自增(詳情請參考 Raft leader election ), 並嘗試在這個紀元中當選。
- 如果當選失敗, 那么在設定的故障遷移超時時間的兩倍之后, 重新嘗試當選。 如果當選成功, 那么執行以下步驟。
- 選出一個從服務器,並將它升級為主服務器。
- 向被選中的從服務器發送
LAVEOF NO ONE
命令,讓它轉變為主服務器。 - 通過發布與訂閱功能, 將更新后的配置傳播給所有其他 Sentinel , 其他 Sentinel 對它們自己的配置進行更新。
- 向已下線主服務器的從服務器發送
SLAVEOF host port
命令, 讓它們去復制新的主服務器。 - 當所有從服務器都已經開始復制新的主服務器時, 領頭 Sentinel 終止這次故障遷移操作。
參考自:
http://redisdoc.com/topic/sentinel.html?highlight=master%20down%20after%20milliseconds