Redis的集群(故障轉移)
故障發現
1. 主觀下線
當cluster-node-timeout時間內某節點無法與另一個節點順利完成ping消息通信時,則將該節點標記為主觀下線狀態。
2. 客觀下線
當某個節點判斷另一個節點主觀下線後,該節點的下線報告會通過Gossip消息傳播。當接收節點發現消息體中含有主觀下線的節點,其會嘗試對該節點進行客觀下線,依據下線報告是否在有效期內(如果在cluster-node-timeout*2時間內無法收集到一半以上槽節點的下線報告,那麽之前的下線報告會過期),且數量大於槽節點總數的一半。若是,則將該節點更新為客觀下線,並向集群廣播下線節點的fail消息。
故障恢復
故障節點變為客觀下線後,如果下線節點是持有槽的主節點,則需要在它的從節點中選出一個替換它,從而保證集群的高可用,過程如下:
1. 資格檢查
每個從節點都要檢查最後與主節點斷線時間,判斷是否有資格替換故障的主節點。如果從節點與主節點斷線時間超過cluster-node-timeout*cluster-slave-validity-factor,則當前從節點不具備故障轉移資格。
2. 準備選舉時間
從節點符合故障轉移資格後,更新觸發故障選舉時間,只有到達該時間才能執行後續流程。采用延遲觸發機制,主要是對多個從節點使用不同的延遲選舉時間來支持優先級。復制偏移量越大說明從節點延遲越低,那麽它應該具有更高的優先級。
3. 發起選舉
當從節點到達故障選舉時間後,會觸發選舉流程:
(1) 更新配置紀元
配置紀元是一個只增不減的整數,每個主節點自身維護一個配置紀元,標示當前主節點的版本,所有主節點的配置紀元都不相等,從節點會復制主節點的配置紀元。整個集群又維護一個全局的配置紀元,用於記錄集群內所有主節點配置紀元的最大版本。每次集群發生重大事件,如新加入主節點或由從節點轉換而來,從節點競爭選舉,都會遞增集群全局配置紀元並賦值給相關主節點,用於記錄這一關鍵事件。
(2) 廣播選舉消息
在集群內廣播選舉消息,並記錄已發送過消息的狀態,保證該從節點在一個配置紀元內只能發起一次選舉。
4. 選舉投票
只有持有槽的主節點才會處理故障選舉消息,每個持有槽的節點在一個配置紀元內都有唯一的一張選票,當接到第一個請求投票的從節點消息,回復消息作為投票,之後相同配置紀元內其它從節點的選舉消息將忽略。投票過程其實是一個領導者選舉的過程。
每個配置紀元代表了一次選舉周期,如果在開始投票後的cluster-node-timeout*2時間內從節點沒有獲取足夠數量的投票,則本次選舉作廢。從節點對配置紀元自增並發起下一輪投票,直到選舉成功為止。
5. 替換主節點
當前從節點取消復制變為主節點,撤銷故障主節點負責的槽,把這些槽委派給自己,並向集群廣播告知所有節點當前從節點變為主節點。
故障轉移時間
1. 主觀下線識別時間=cluster-node-timeout。
2. 主觀下線狀態消息傳播時間<=cluster-node-timeout/2(消息通信機制會優先選取下線狀態節點通信)。
3. 從節點轉移時間<=1000毫秒(偏移量最大的從節點最多延遲1秒發起選舉,通常一次就會成功)。
所以,failover-time(毫秒) <= cluster-node-timeout + cluster-node-timeout/2 + 1000
故障轉移過程
以Redis的集群(伸縮)搭建好的集群模擬主節點故障場景。
集群狀態信息
127.0.0.1:6879> cluster nodes
99ea0df1d9683affb1271a5092fc8b15b378adba 127.0.0.1:6885 master - 0 1533780841246 12 connected 0-1364 4096 5461-6826 10923-12287 15018-16383
558b0fb8d44933e694b46c15d05e595ce5ae4fab 127.0.0.1:6886 slave 99ea0df1d9683affb1271a5092fc8b15b378adba 0 1533780844286 12 connected
...
強制關閉6885端口對應的redis進程
$ ps -ef | grep 6885
redis 96825 1 0 Aug04 ? 00:05:03 redis-server 0.0.0.0:6885 [cluster]
$ kill -9 96825
日誌分析如下:
. 從節點6886與主節點6885復制中斷。
96829:S 08 Aug 19:15:45.870 # Connection with master lost.
96829:S 08 Aug 19:15:45.870 * Caching the disconnected master state.
96829:S 08 Aug 19:15:46.804 * Connecting to MASTER 127.0.0.1:6885
96829:S 08 Aug 19:15:46.805 * MASTER <-> SLAVE sync started
96829:S 08 Aug 19:15:46.805 # Error condition on socket for SYNC: Connection refused
. 6879和6880兩個主節點都標記6885為主觀下線,超過半數因此標記為客觀下線狀態。
6879端口日誌
22574:M 08 Aug 19:16:02.677 * Marking node 99ea0df1d9683affb1271a5092fc8b15b378adba as failing (quorum reached).
6880端口日誌
22578:M 08 Aug 19:16:02.680 * Marking node 99ea0df1d9683affb1271a5092fc8b15b378adba as failing (quorum reached).
. 從節點識別正在復制的主節點進入客觀下線後準備選舉時間。
6886端口日誌
96829:S 08 Aug 19:16:02.682 * FAIL message received from 90cb860b7f4ff516304c577bc1e514dc95ecd09b about 99ea0df1d9683affb1271a5092fc8b15b378adba
96829:S 08 Aug 19:16:02.761 # Start of election delayed for 855 milliseconds (rank #0, offset 1654026).
. 延遲選舉時間到達後,從節點更新配置紀元並發起故障選舉。
6886端口日誌
96829:S 08 Aug 19:16:03.677 # Starting a failover election for epoch 13.
. 6879和6880主節點為從節點6886投票。
6879端口日誌
22574:M 08 Aug 19:16:03.679 # Failover auth granted to 558b0fb8d44933e694b46c15d05e595ce5ae4fab for epoch 13
6880端口日誌
22578:M 08 Aug 19:16:03.680 # Failover auth granted to 558b0fb8d44933e694b46c15d05e595ce5ae4fab for epoch 13
. 從節點獲取2個主節點投票後,超過半數,執行替換主節點操作從而完成故障轉移。
6886端口日誌
96829:S 08 Aug 19:16:03.688 # Failover election won: I'm the new master.
96829:S 08 Aug 19:16:03.688 # configEpoch set to 13 after successful failover
故障轉移後,集群狀態信息
127.0.0.1:6879> cluster nodes
99ea0df1d9683affb1271a5092fc8b15b378adba 127.0.0.1:6885 master,fail - 1533780945879 1533780943442 12 disconnected
558b0fb8d44933e694b46c15d05e595ce5ae4fab 127.0.0.1:6886 master - 0 1533803267149 13 connected 0-1364 4096 5461-6826 10923-12287 15018-16383
...
重啟6885端口,其會以現有集群配置為準,變為新主節點6886的從節點。
6885端口日誌
47931:M 09 Aug 01:35:34.512 # Configuration change detected. Reconfiguring myself as a replica of 558b0fb8d44933e694b46c15d05e595ce5ae4fab
集群內其它節點接收到6885發來的ping消息,清空客觀下線狀態。
22574:M 09 Aug 01:35:34.520 * Clear FAIL state for node 99ea0df1d9683affb1271a5092fc8b15b378adba: master without slots is reachable again.
此時集群狀態信息
127.0.0.1:6879> cluster nodes
99ea0df1d9683affb1271a5092fc8b15b378adba 127.0.0.1:6885 slave 558b0fb8d44933e694b46c15d05e595ce5ae4fab 0 1533804408406 13 connected
558b0fb8d44933e694b46c15d05e595ce5ae4fab 127.0.0.1:6886 master - 0 1533804407394 13 connected 0-1364 4096 5461-6826 10923-12287 15018-16383
...
手動故障轉移
Redis集群提供了手動故障轉移功能,指定從節點發起轉移,主從節點角色進行互換,過程如下:
1. 從節點通知主節點停止處理所有客戶端請求。
2. 主節點發送對應從節點延遲復制的數據。
3. 從節點接收復制延遲的數據,直到主從復制偏移量一致。
4. 從節點立刻發起投票選舉,選舉成功後斷開復制變為新的主節點,之後向集群廣播。
5. 原主節點接收消息後更新自身配置變為從節點,解除所有客戶端請求阻塞,重定向到新的主節點。
6. 原主節點變為從節點後,向新的主節點發起全量復制請求(Redis4.0版本這一過程會有改善)。
6885端口上發起手動轉移
127.0.0.1:6885> cluster failover
OK
日誌分析如下:
6885端口日誌
47931:S 09 Aug 02:11:19.943 # Manual failover user request accepted.
47931:S 09 Aug 02:11:20.010 # Received replication offset for paused master manual failover: 2969
47931:S 09 Aug 02:11:20.011 # All master replication stream processed, manual failover can start.
47931:S 09 Aug 02:11:20.011 # Start of election delayed for 0 milliseconds (rank #0, offset 2969).
47931:S 09 Aug 02:11:20.111 # Starting a failover election for epoch 14.
47931:S 09 Aug 02:11:20.115 # Failover election won: I'm the new master.
47931:S 09 Aug 02:11:20.115 # configEpoch set to 14 after successful failover
47931:M 09 Aug 02:11:20.115 # Connection with master lost.
6886端口日誌
96829:M 09 Aug 02:11:19.943 # Manual failover requested by slave 99ea0df1d9683affb1271a5092fc8b15b378adba.
96829:M 09 Aug 02:11:20.113 # Failover auth granted to 99ea0df1d9683affb1271a5092fc8b15b378adba for epoch 14
96829:M 09 Aug 02:11:20.116 # Connection with slave 127.0.0.1:6885 lost.
96829:M 09 Aug 02:11:20.318 # Configuration change detected. Reconfiguring myself as a replica of 99ea0df1d9683affb1271a5092fc8b15b378adba
96829:S 09 Aug 02:11:20.521 * Connecting to MASTER 127.0.0.1:6885
Redis集群還提供了強制故障轉移的方法:
1. cluster failover force - 用於主節點宕機且無法自動完成故障轉移的情況。
2. cluster failover takeover - 用於集群內一半以上主節點故障的場景,從節點無法收到半數以上主節點投票,無法完成選舉過程(慎用)。
若感興趣可關註訂閱號”數據庫最佳實踐”(DBBestPractice).
Redis的集群(故障轉移)