盡管我們已經知道了主從復制能盡可能的確保數據不會因master node的節點掛掉而丟失,然而現實生活中,我們總不可能一直在看着master node的狀態,在一旁等着它掛掉然后把它“提起來”。這時候,需要有個東西來替我們監督並且隨時調整redis集群狀況,這個東西就叫做哨兵(sential)。
哨兵是redis集群架構中非常重要的一個組件,主要功能如下:
(1)集群監控,負責監控redis master和slave進程是否正常。
(2)消息通知,如果某個redis實例有故障,那么哨兵負責發送消息作為報警通知給管理員。
(3)故障轉移,如果發現master node掛了,會自動啟動一個slave node,讓它轉換為master node
(4)配置中心,如果故障轉移發生了,通知client客戶端有新 的master地址。
1.哨兵集群
當然啦,哨兵本身也是分布式的,組成一個哨兵集群去運行,互相協同工作。作為集群,主要有2個方面,一個是當故障轉移時,判斷一個master node時宕機了,需要大部分的哨兵都同意才行,這涉及到了slave選舉(后面會講到);另一個是,要保證即使部分哨兵節點掛掉了,哨兵集群還是能正常工作的,想想看也知道,如果作為一個高可用機制重要組成部分的故障轉移系統本身就是單節點的,一點都不高可用,那就很坑爹了。
注:目前采用的是sentinal 2版本,相對於1版本,重寫了很多代碼。主要是讓故障轉移的機制和算法變得更加健壯和簡單。
哨兵集群的注意事項:
(1)哨兵至少需要3個實例來保證自己的健壯性。
(2)哨兵+redis主從的部署架構,是不能保證數據零丟失的,只能保證redis集群的高可用性。
(3)對於哨兵+redis主從這種復雜的部署架構,盡量在測試環境和生產環境,都進行充足的測試和演練。
這里先簡單的解釋下為什么redis哨兵集群必須要部署2個以上的節點(2個不行)。假設我們部署2個節點,一個主,一個從,每個節點上有哨兵,如圖(M代表主,R代表從,S1、S2代表哨兵)
--------------|---------------
| M1 | | | R1 |
| S1 | | | S2 |
---------------|----------------
部署哨兵集群的時候,有個參數叫quorum,quorum = 1,意思就是如果master宕機,s1和s2中只要有1個哨兵認為master宕機就可以進行主從切換,同時s1和s2中會選出一個來執行故障轉移。
同時這個時候,需要majority,也就是大多數哨兵都是運行的,2個哨兵的majority就是2(3個是2,4個是2,5個是3....),這里有2個哨兵都運行,那就可以故障轉移了。
但是如果其中一個機器宕機了,就沒有majority來允許執行故障轉移了。
其實就是運行的哨兵集群無法滿足majority這個先天條件,就無法進行故障轉移。這里啰嗦了這么多,就是想讓大家有個quorum這個概念,因為下面我們會屢次講到。
2.Redis哨兵主備切換的數據丟失問題
哨兵集群在發現master node掛掉后會進行故障轉移,也就是啟動其中一個slave node為master node。在這過程中,可能會導致數據丟失的情況,主要有兩種。
(1)異步復制導致的數據丟失
我們已經知道了,master->slave的復制是異步,所以可能有部分還沒來得及復制到slave就宕機了,此時這些部分數據就丟失了。
(2)腦裂導致的數據丟失
腦裂,也就是說,某個master所在機器突然脫離了正常的網絡,跟其它slave機器不能連接,但是實際上master還運行着。
此時哨兵可能就會認為master宕機了,然后開始選舉,講其它slave切換成master。這時候集群里就會有2個master,也就是所謂的腦裂。
此時雖然某個slave被切換成了master,但是可能client還沒來得及切換成新的master,還繼續寫向舊的master的數據可能就丟失了。
因此舊master再次恢復的時候,會被作為一個slave掛到新的master上去,自己的數據會被清空,重新從新的master復制數據
(3)如何解決異步復制和腦裂導致的數據丟失問題
min-slaves-to-write 1 min-slaves-max-lag 10
這兩個參數的意思就是,要求至少有1個slave,數據復制和同步的延遲不能超過10s,如果說一旦所有的slave,數據復制和同步的延遲都超過10s,那么這個時候,master就不會再接受任何請求了。
有了min-slaves-max-lag這個配置,就可以確保的說,一旦slave復制數據和ack延遲時間太長,就認為可能master宕機后損失的數據太多了,那么就拒絕寫請求,這樣就可以把master宕機時由於部分數據未同步到slave導致的數據丟失降低到可控范圍內。
如果一個master出現了腦裂,跟其他slave丟了連接,那么上面兩個配置可以確保的說,如果不能繼續給指定數量的slave發送數據,而且slave超過10秒沒有給自己ack消息,那么就直接拒絕客戶端的寫請求。這樣腦裂后的舊master就不會接受client的新數據,也就避免了數據丟失。
上面的配置就確保了,如果跟任何一個slave丟了連接,在10秒后發現沒有slave給自己ack,那么就拒絕新的寫請求,因此在腦裂場景下,最多就丟失10秒的數據。
3.哨兵的核心底層原理
(1)sdown和odown轉換機制
- sdown是主觀宕機,就一個哨兵覺得一個master宕機了,那么就是主觀宕機。
- odown是客觀宕機,如果quorum數量的哨兵都覺得一個master宕機了,那么就是客觀宕機。
- sdown達成的條件非常簡單,如果一個哨兵ping一個master,超過了is-master-down-after-milliseconds指定的毫秒數之后,就主觀認為master宕機。odown同理。
(2)哨兵集群的自動發現機制
哨兵互相之間的發現,是通過redis的pub/sub系統實現的,每個哨兵都會往__sentinel__:hello這個channel里發送一個消息,這時候所有其他哨兵都可以消費到這個消息,並感知到其他的哨兵的存在。
具體就是,每隔2s,每個哨兵都會往自己監控的某個master+slaves對應的__sentinel__:hello channel里發送一個消息,內容是自己的host、ip和runid還有對這個master的監控配置。然后呢,每個哨兵也會去監聽自己監控的每個master+slaves對應的__sentinel__:hello channel,然后去感知到同樣在監聽這個master+slaves的其他哨兵的存在。
每個哨兵還會跟其他哨兵交換對master的監控配置,互相進行監控配置的同步。
(3)slave配置的自動糾正
哨兵會負責自動糾正slave的一些配置,比如slave如果要成為潛在的master候選人,哨兵會確保slave在復制現有master的數據,如果slave連接到了一個錯誤的master上,比如故障轉移之后,哨兵會確保它們連接到正確的master上。
(4)slave->master選舉算法
如果一個master被認為odown了,而且majority哨兵都允許了主備切換,那么某個哨兵就會執行主備切換操作,此時要選舉一個slave來。
首先,會考慮slave的一些信息
- 跟master斷開連接的時長
- slave優先級
- 復制offset
- run id
如果一個slave跟master斷開連接已經超過了down-after-milliseconds的10倍,外加master宕機的時長,那么slave就被認為不適合選舉為master。
公式:
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下來會對slave進行排序
- 按照slave優先級進行排序,slave priority越低,優先級就越高;
- 如果slave priority相同,那么看replica offset,哪個slave復制了越多的數據,offset越靠后,優先級就越高;
- 如果上面兩個條件都相同,那么選擇一個run id比較小的那個slave。
(5)configuration epoch
哨兵會對一套redis master+slave進行監控,有相應的監控的配置。
執行切換的那個哨兵,會從要切換到的新master(salve->master)那里得到一個configuration epoch,這就是一個version號,每次切換的version號都必須是唯一的。
如果第一個選舉出的哨兵切換失敗了,那么其他哨兵,會等待failover-timeout時間,然后接替繼續執行切換,此時會重新獲取一個新的configuration epoch,作為新的version號
(6)configuraiton傳播
哨兵完成切換之后,會在自己本地更新生成最新的master配置,然后同步給其他的哨兵,就是通過之前說的pub/sub消息機制
這里之前的version號就很重要了,因為各種消息都是通過一個channel去發布和監聽的,所以一個哨兵完成一次新的切換之后,新的master配置是跟着新的version號的
其他的哨兵都是根據版本號的大小來更新自己的master配置的。
