您好,登錄后才能下訂單哦!
小編今天帶大家了解如何分析redis中的高可用方案,文中知識點介紹的非常詳細。覺得有幫助的朋友可以跟著小編一起瀏覽文章的內容,希望能夠幫助更多想解決這個問題的朋友找到問題的答案,下面跟著小編一起深入學習“如何分析redis中的高可用方案”的知識吧。
用戶可以通過SLAVEOF命令或者配置,讓一個服務器去復制另一個服務器。被復制的服務器稱為主服務器,進行復制的服務器稱為從服務器。這樣你在主服務器上增加鍵值,同時可以在從服務器上讀取。【相關推薦:Redis視頻教程】
復制的過程又分為同步和命令傳播兩個步驟。
同步
同步將從服務器的數據庫狀態更新到主服務器當前的數據庫狀態。
客戶端向從服務器發送SLAVEOF命令時,從服務器會向主服務器發生SYNC命令
進行同步,步驟如下:
從服務器向主服務器發生SYNC命令。
收到SYNC命令的主服務器執行BGSAVE命令,在后臺生成一個RDB文件,并用一個緩沖區記錄從現在開始執行的所有寫命令。
主服務器的BGSAVE命令執行完畢后,主服務器將BGSAVE生成的RDB文件發送給從服務器,從服務器接收并載入這個RDB文件,將從服務器的數據庫狀態更新到主服務器執行BGSAVE命令時的數據庫狀態
主服務器將緩沖區的所有寫命令發送給從服務器,從服務器執行這些寫命令,將數據庫狀態更新至主服務器當前數據庫狀態。
命令傳播
同步操作完成之后,主服務器和從服務器的數據庫狀態是一致的,但主服務器又接收到客戶端寫命令后,主從數據庫之間又產生了數據不一致,這時通過命令傳播達到數據庫一致。
PSYNC同步的優化
2.8之前的同步每次都是全量同步,而如果是從服務器只是斷開連接了一會,事實上是不用從頭開始同步的,只需要將斷開連接這會的數據同步即可。所以2.8版本開始使用PSYNC來代替SYNC命令。
PSYNC分成全量同步和部分同步兩種情況,全量同步就是處理初次同步的狀態,而部分同步就是處理斷線重連這種情況。
部分同步的實現
部分同步主要使用了以下三部分:
主服務器的復制偏移量和從服務器的復制偏移量
主服務器的復制積壓緩沖區
服務器的運行ID
復制偏移量
主服務器的復制偏移量:主服務器每次向從服務器傳播N個字節的數據時,就將自己的復制偏移量+N從服務器的復制偏移量:從服務器每次收到主服務器傳播的N個字節數據,就將自己的復制偏移量+N 如果主從服務器處于一致狀態,那么它們的偏移量總是相同的,如果偏移量不相等,那么說明它們處于不一致狀態。
復制積壓緩沖區
復制積壓緩沖區由主服務器維護的一個固定長度的FIFO隊列,默認大小1MB,達到最大長度后,最先入隊的會被彈出,給新入隊的元素讓位置。
redis命令傳播的時候不但會發送給從服務器,還會發送給復制積壓緩沖區。
當從服務器重連上主服務器時,從服務器會通過PSYNC命令將自己的復制偏移量offset發送給主服務器,主服務器根據復制偏移量來決定使用部分同步還是全量同步。
如果offset偏移量之后的數據還在復制積壓緩沖區,那么使用部分同步,反之使用全量同步。
(書上沒說是怎么判斷的,我猜測應該是拿主復制偏移量減去從復制偏移量,如果大于1MB就說明有數據不在緩沖積壓區?)
服務器的運行ID
服務器啟動時會生成一個40位隨機的字符作為服務器運行ID。
從服務器對主服務器初次復制時,主服務器會將自己的運行ID傳送給從服務器,而從服務器會將這個運行ID保存下來。從服務器斷線重連的時候,會將保存的運行ID發送過去,如果從服務器保存的運行ID和當前主服務器的運行ID相同,那么會嘗試部分同步,如果不同會執行全量同步。
PSYNC的整體流程
心跳檢測
在命令傳播階段,從服務器會默認以每秒一次的頻率,向主服務器發送命令:REPLICONF ACK <replication_offset>
其中replication_offset就是從服務器當前的復制偏移量。
發送REPLICONF ACK命令對于主從服務器有三個作用:
檢測主從服務器的網絡連接狀態。
輔助實現min-slaves選項。
檢測命令丟失。
檢測主從服務器的網絡連接狀態
主從服務器可以通過發送和接收REPLICONF ACK命令來檢查兩者之間的網絡連接是否正常:如果主服務器超過一秒鐘沒有收到從服務器發來的REPLICONF ACK命令,那么主服務器就知道主從之間出現問題了。
輔助實現min-slaves選項
redis的min-slaves-to-write
和min-slaves-max-lag
兩個選項可以防止主從服務器在不安全的情況下執行寫命令。
min-slaves-to-write 3 min-slaves-max-lag 10
如果配置如上,就表示如果從服務器的數量少于3個,或者3個從服務器的延遲都大于或等于10秒時,那么主服務器就將拒絕執行寫命令。
檢測命令丟失
如果因為網絡故障,主服務器傳播給從服務器的寫命令在半路丟失,那么從服務器向主服務器發送REPLICONF ACK命令時,主服務器將發覺從服務器當前的復制偏移量少于自己的偏移量,那么主服務器可以根據從服務器的復制偏移量,在復制緩沖區當中找到從服務器缺少的數據,將這些數據重寫發送給從服務器。
主從復制總結
其實主從復制就是多備份了一份數據,因為即使有RDB和AOF進行持久化,但是可能主服務器上整個機器掛掉了,而主從復制可以將主從服務器部署在兩臺不同的機器上,這樣即使主服務器的機器掛掉了,也可以手動切換到從服務器繼續服務。
主從雖然實現了數據的備份,但當主服務器掛掉時,需要手動的將從服務器切換成主服務器。而sentinel就可以實現當主服務器掛掉時,自動將從服務器切換成主服務器。
sentinel系統可以監視所有的主從服務器,假設server1現在下線。當server1的下線時長超過用戶設定的下線時長上限時,sentinel系統就會對server1執行故障轉移:
首先sentinel系統會挑選server1下的其中一個從服務器,并將這個選中的從服務器升級成新的主服務器。
之后,sentinel系統會向server1屬下的所有從服務器發送新的復制命令,讓他們成為新主服務器的從服務器。當所有從服務器復制新的主服務器時,故障轉移操作執行完畢。
另外,sentinel還會監視已下線的server1,在它重新上線時,將它設置為新的主服務器的從服務器。
初始化sentinel狀態
struct sentinelState { char myid[CONFIG_RUN_ID_SIZE+1]; // 當前紀元,用于實現故障轉移 uint64_t current_epoch; // 保存了所有被這個sentinel監視的主服務器 // 字典的鍵是主服務器的名字 // 字典的值是指向sentinelRedisInstance結構的指針 dict *masters; // 是否進入了TILT模式 int tilt; // 目前正在執行的腳本數量 int running_scripts; // 進入TILT模式的時間 mstime_t tilt_start_time; // 最后一次執行時間處理器的時間 mstime_t previous_time; // 一個fifo隊列,包含了所有需要執行的用戶腳本 list *scripts_queue; char *announce_ip; int announce_port; unsigned long simfailure_flags; int deny_scripts_reconfig; char *sentinel_auth_pass; char *sentinel_auth_user; int resolve_hostnames; int announce_hostnames; } sentinel;
初始化sentinel狀態的masters屬性
masters記錄了所有被sentinel監視的主服務器的相關信息,其中字典的鍵是被監視服務器的名字,而值是被監視服務器對應著sentinelRedisInstance結構。sentinelRedisInstance被sentinel服務器監視的實例,可以是主服務器、從服務器或其他sentinel實例。
typedef struct sentinelRedisInstance { // 標識值,記錄實例的類型,以及該實例的當前狀態 int flags; // 實例的名字 // 主服務器名字在配置文件中設置 // 從服務器和sentinel名字由sentinel自動設置,格式是ip:port char *name; // 運行id char *runid; // 配置紀元,用于實現故障轉移 uint64_t config_epoch; // 實例的地址 sentinelAddr *addr; /* Master host. */ // 實例無響應多少毫秒之后,判斷為主觀下線 mstime_t down_after_period; // 判斷這個實例為客觀下線所需的支持投票數量 unsigned int quorum; // 執行故障轉移,可以同時對新的主服務器進行同步的從服務器數量 int parallel_syncs; // 刷新故障遷移狀態的最大時限 mstime_t failover_timeout; // 除了自己外,其他監視主服務器的sentinel // 鍵是sentinel的名字,格式是ip:port // 值是鍵對應的sentinel的實例結構 dict *sentinels; // ... } sentinelRedisInstance;
創建連向主服務器的網絡連接
初始化sentinel的最后一步是創建連向被監視主服務器的網絡連接,會創建兩個連向主服務器的連接。
命令連接:專門向主服務器發送命令,并接收命令回復。
訂閱連接:專門用于訂閱主服務器的_sentinel_:hello頻道。
獲取主服務器信息
sentinel默認會每10秒,通過命令連接向被監視的主服務器發送INFO命令,并通過回復獲取主服務器當前的信息。回復可以獲得以下信息。
主服務器的run_id
主服務器下所有從服務器的信息。
根據這些信息可以更新sentinelRedisInstance下的name字典和runid字段。
獲取從服務器信息
sentinel也會創建連接到從服務器的命令連接和訂閱連接。
sentinel默認會每10秒,通過命令連接向從服務器發送INFO命令,并通過回復獲取從服務器當前的信息。回復如下:
從服務器的運行ID
從服務器的角色role
主服務器的ip和端口
主服務器的連接狀態master_link_status
從服務器的優先級slave_priority
從服務器的復制偏移變量
根據info的回復信息,sentinel可以更新從服務器的實例結構。
向主服務器和從服務器的訂閱連接發送信息
默認情況下,sentinel會每2秒一次,向被監視的主服務器和從服務器發送命令。
s_ip
:sentinel的ip地址s_port
:sentinel的端口號s_runid
:sentinel的運行ids_epoch
:sentinel當前的配置紀元m_name
:主服務器的名字m_ip
:主服務器的ip地址m_port
:主服務器的端口號m_epoch
:主服務器當前的配置紀元
向sentinel_:hello頻道發送信息,也會被監視同一個服務器的其他sentinel監聽到(包括自己)。
創建連向其他sentinel的命令連接
sentinel之間會互相創建命令連接。監視同一個囑咐其的多個sentinel將形成相互連接的網絡。
sentinel之間不會創建訂閱連接。
檢測主觀下線狀態
sentinel會每秒一次向所有與它創建了命令連接的實例(主服務器、從服務器、其他sentinel)發送ping命令,通過實例的回復來判斷實例是否在線。
有效回復:實例返回+PONG、-LOADING、-MASTERDOWN其中一種。
無效回復:以上三種回復之外的其他回復,或者指定時長內沒回復。
某個實例在down-after-milliseconds
毫秒內,連續向sentinel返回無效回復。那么sentinel就會修改這個實例對應的實例結構,在結構的flags屬性中打開SRI_S_DOWN標識,標識該實例進入主觀下線狀態。(down-after-milliseconds可以在sentinel的配置文件中配置)
檢測客觀下線狀態
當sentinel將一個主服務器判斷為主觀下線后,為了確認這個主服務器是否真的下線,還會想其他同樣監視這個主服務器的其他sentinel詢問,看其他sentinel是否也認為該主服務器下線了。超過一定數量就將主服務器判斷為客觀下線。
詢問其他sentinel是否同意該服務器下線
SENTINEL is-master-down-by-addr <ip><port><current_epoch><runid>
通過SENTINEL is-master-down-by-addr命令詢問,參數意義如下圖:
接收SENTINEL is-master-down-by-addr命令
其他sentinel接收到SENTINEL is-master-down-by-addr命令后,會根據其中主服務器的ip和端口,檢查主服務器是否下線,然后返回包含三個參數的Multi Bulk的回復。
sentinel統計其他sentinel同意主服務器已下線的數量,達到配置的數量后,則將主服務器的flags屬性的SRI_O_DOWN標識打開,表示主服務器已經進入客觀下線狀態。
選舉領頭sentinel
當一個主服務器被判斷成客觀下線時,監視這個下線主服務器的各個sentinel就會協商選舉一個新的領頭sentinel,由這個sentinel進行故障轉移操作。
確認主服務器進入客觀下線狀態后,會再次發送SENTINEL is-master-down-by-addr命令來選舉出領頭sentinel。
選舉規則
監視同一個主服務器的多個在線sentinel中每一個都可能成為領頭sentinel。
每次進行領頭sentinel選舉之后,無論選舉是否成功,所有sentinel的配置紀元(configuration epoch)的值都會自增一次。(配置紀元,其實就是一個計數器)
在一個配置紀元里,所有sentinel都有將某個sentinel設置成局部sentinel的機會,一旦設置在這個配置紀元里就不能再更改。
所有發現主服務器客觀下線的sentinel都會要求其他sentinel將自己設置為局部領頭sentinel,也就是都會發送SENTINEL is-master-down-by-addr命令,嘗試讓其他sentinel將自己設置成局部領頭sentinel。
當一個sentinel向另一個sentinel發送SENTINEL is-master-down-by-addr
命令時,如果runid參數的值不是*,而是源sentinel的runid,就表示要目標sentinel將自己設置成領頭sentinel。
sentinel設置局部領頭的規則是先到先得
,第一個設置為局部領頭sentinel后,其他的請求都被拒絕。
目標sentinel在接收到一條SENTINEL is-master-down-by-addr命令后,將向源sentinel返回一個命令回復。回復中leader_runid參數和leader_epoch參數分別記錄了目標sentinel的局部領頭sentinel的runid和配置紀元。
源sentinel接收到回復之后,會比較返回的配置紀元是否和自己的配置紀元相同,如果一樣再繼續比較返回的局部領頭sentinel的runid是否和自己的runid相同,如果一致就表示目標sentinel將自己設置成了局部領頭sentinel。
如果某個sentinel被半數以上的sentinel設置成了局部領頭sentinel,那么它就成為領頭sentinel。
領頭sentinel需要半數以上支持,并且每個配置紀元內只能設置一次,那么一個配置紀元里,只會出現一個領頭sentinel
如果在一定時限內,每一個sentinel被選舉成領頭sentinel(沒人沒獲取半數以上選票),那么各個sentinel在一段時間之后再次選舉,直到選出領頭sentinel
故障轉移
故障轉移包含以下三個步驟:
在已下線的主服務器下所有從服務器里,挑選出一個從服務器轉換成主服務器。
讓已下線的主服務器屬下的所有從服務器改為復制新的主服務器。
將已經下線的主服務器設置為新服務器的從服務器,舊的主服務器重新上線后,它就成為新的主服務器的從服務器。
選出新的主服務器
已下線的主服務器下所有從服務器里,挑選出一個從服務器,向這個從服務器發送SLAVEOF no one命令,將這個從服務器轉換成主服務器。
挑選新主服務器的規則
領頭的sentinel會將已下線主服務器的所有從服務器保存到一個列表里面,然后對這個列表進行過濾,挑選出新的主服務器。
刪除列表中所有處于下線或者斷線狀態的從服務器。
刪除列表中所有最近五秒內沒有回復過領頭sentinel的INFO命令的從服務器
刪除所有與已下線服務器連接斷開超過 dwon-after-milliseconds * 10毫秒的服務器
然后根據從服務器的優先級,對列表中剩余的從服務器進行排序,并選出其中優先級最高的服務器。
如果有多個相同最高優先級的從服務器,那么就根據復制偏移量進行排序,選出最大偏移量的從服務器(復制偏移量最大也代表它保存的數據最新)
如果復制偏移量也相同,那么就根據runid進行排序,選其中runid最小的從服務器
發送slaveof no one 命令之后,領頭sentinel會每秒一次向被升級的從服務器發送info命令(平常是每10秒一次),如果返回的回復role從原來的slave變成了master,那么領頭sentinel就知道從服務器已經升級成主服務器了。
修改從服務器的復制目標
通過SLAVEOF命令來使從服務器復制新的主服務器。當sentinel監測到舊的主服務器重新上線后,也會發送SLAVEOF命令使它成為新的主服務器的從服務器。
sentinel總結
sentinel其實就是一個監控系統,,而sentinel監測到主服務器下線后,可以通過選舉機制選出一個領頭的sentinel,然后由這個領頭的sentinel將下線主服務器下的從服務器挑選一個切換成主服務器,而不用人工手動切換。
哨兵模式雖然做到了主從自動切換,但是還是只有一臺主服務器進行寫操作(當然哨兵模式也可以監視多個主服務器,但需要客戶端自己實現負載均衡)。官方也提供了自己的方式實現集群。
節點
每個redis服務實例就是一個節點,多個連接的節點組成一個集群。
CLUSTER MEET <ip><port>
向另一個節點發送CLUSTER MEET命令,可以讓節點與目標節點進行握手,握手成功就能將該節點加入到當前集群。
啟動節點
redis服務器啟動時會根據cluster-enabled配置選項是否為yes來決定是否開啟服務器集群模式。
集群數據結構
每個節點都會使用一個clusterNode結構記錄自己的狀態,并為集群中其他節點都創建一個相應的clusterNode結構,記錄其他節點狀態。
typedef struct clusterNode { // 創建節點的時間 mstime_t ctime; // 節點的名稱 char name[CLUSTER_NAMELEN]; // 節點標識 // 各種不同的標識值記錄節點的角色(比如主節點或從節點) // 以及節點目前所處的狀態(在線或者下線) int flags; // 節點當前的配置紀元,用于實現故障轉移 uint64_t configEpoch; // 節點的ip地址 char ip[NET_IP_STR_LEN]; // 保存建立連接節點的有關信息 clusterLink *link; list *fail_reports; // ... } clusterNode;
clusterLink保存著連接節點所需的相關信息
typedef struct clusterLink { // ... // 連接的創建時間 mstime_t ctime; // 與這個連接相關聯的節點,沒有就為null struct clusterNode *node; // ... } clusterLink;
每個節點還保存著一個clusterState結構,它記錄了在當前節點視角下,集群目前所處的狀態,例如集群在線還是下線,集群包含多少個節點等等。
typedef struct clusterState { // 指向當前節點clusterNode的指針 clusterNode *myself; // 集群當前的配置紀元,用于實現故障轉移 uint64_t currentEpoch; // 集群當前的狀態,上線或者下線 int state; // 集群中至少處理一個槽的節點數量 int size; // 集群節點的名單(包括myself節點) // 字典的鍵是節點的名字,字典的值為節點對應的clusterNode結構 dict *nodes; } clusterState;
CLUSTER MEET 命令的實現
CLUSTER MEET <ip><port>
節點 A 會為節點 B 創建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes 字典里面。
之后,節點 A 將根據 CLUSTER MEET 命令給定的 IP 地址和端口號,向節點 B 發送一條 MEET 消息。
如果一切順利,節點 B 將接收到節點 A 發送的 MEET 消息,節點 B 會為節點 A 創建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes字典里面。
之后,節點 B 將向節點 A 返回一條 PONG 消息。
如果一切順利,節點 A 將接收到節點 B 返回的 PONG 消息,通過這條 PONG 消息節點 A 可以知道節點 B 已經成功地接收到了自己發送的 MEET 消息。
之后,節點 A 將向節點 B 返回一條 PING 消息。
如果一切順利,節點B將接收到節點A返回的PING消息,通過這條PING消息節點B知道節點A已經成功接收到自己返回的PONG消息,握手完成。
槽指派
集群的整個數據庫被分為16384個槽,每個鍵都屬于16384個槽的其中一個,集群中每個節點處理0個或16384個槽。當所有的槽都有節點在處理時,集群處于上線狀態,否則就是下線狀態。
CLUSTER ADDSLOTS
CLUSTER ADDSLOTS <slot>...
通過CLUSTER ADDSLOTS命令可以將指定槽指派給當前節點負責,例如:CLUSTER ADDSLOTS 0 1 2 3 4 可以將0至4的槽指派給當前節點
記錄節點的槽指派信息
clusterNode結構的slots屬性和numslot屬性記錄了節點負責處理哪些槽:
typedef struct clusterNode { unsigned char slots[CLUSTER_SLOTS/8]; int numslots; // ... } clusterNode;
slots:
是一個二進制數組,一共包含16384個二進制位。當二進制位的值是1,代表節點負責處理該槽,如果是0,代表節點不處理該槽numslots:
numslots屬性則記錄節點負責處理槽的數量,也就是slots中值為1的二進制位的數量。
傳播節點的槽指派信息
節點除了會將自己負責的槽記錄在clusterNode中,還會將slots數組發送給集群中的其他節點,以此告知其他節點自己目前負責處理哪些槽。
typedef struct clusterState { clusterNode *slots[CLUSTER_SLOTS]; } clusterState;
slots包含16384個項,每一個數組項都是指向clusterNode的指針,表示被指派給該節點,如果未指派給任何節點,那么指針指向NULL。
CLUSTER ADDSLOTS命令的實現
在集群中執行命令
客戶端向節點發送與數據庫有關的命令時,接收命令的節點會計算出命令要處理的數據庫鍵屬于哪個槽,并檢查該槽是否指派給了自己。
如果指派給了自己,那么該節點直接執行該命令。如果沒有,那么該節點會向客戶端返回一個MOCED的錯誤,指引客戶端轉向正確的節點,并再次發送執行的命令。
計算鍵屬于那個槽
CRC16(key)是計算出鍵key的CRC16的校驗和,而 & 16383就是取余,算出0-16383之間的整數作為鍵的槽號。
判斷槽是否由當前節點負責處理
計算出鍵所屬的槽號i后,節點就能判斷該槽號是否由自己處理。
如果clusterState.slots[i]等于如果clusterState.myself,那么由自己負責該節點可以直接執行命令。
如果不相等,那么可以獲取clusterState.slots[i]指向如果clusterNode的ip和端口,向客戶端返回MOVED錯誤,指引客戶端轉向負責該槽的節點。
集群模式下不會打印MOVED錯誤,而是直接自動轉向。
重新分片
redis集群重新分配可以將任意數量已經指派給某個節點的槽改為指派給另一個節點,相關槽所屬的鍵值對也會從源節點移動到目標節點。
重新分片操作是在線進行的,在重新分片的過程中,集群不用下線,源節點和目標節點都可以繼續處理命令請求。
redis集群的重新分片操作是由redis-trib負責執行。重新分片執行步驟如下:
redis-trib對目標節點發送CLUSTER SETSLOT <slot> IMPORTING <source_id>
命令,讓目標節點準備好從源節點導入槽slot的鍵值對。
redis-trib對源節點發送CLUSTER SETSLOT <slot> MIGRTING <target_id>
命令,讓源節點準備好將屬于槽slot的鍵值對遷移至目標節點。
redis-trib向源節點發送CLUSTER GETKEYSINSLOT <slot> <count>
命令,獲取最多count個屬于槽的鍵值對的鍵名稱。
對于步驟3獲取的每個鍵名,redis-trib都向源節點發送一個MIGRTING <target_ip> <target_port> <key_name> 0 <timeout>
命令,將被選中的鍵值對從源節點遷移至目標節點。
重復執行步驟3和步驟4,直到源節點保存的所以屬于槽slot的鍵值對都被遷移至目標節點。
redis-trib向集群中任何一個節點發送CLUSTER SETSLOT <slot> NODE <target_id>
命令,將槽指派給目標節點。這一信息最終會通過消息發送至整個集群。
CLUSTER SETSLOT IMPORTING 命令實現
typedef struct clusterState { // ... clusterNode *importing_slots_from[CLUSTER_SLOTS]; } clusterState;
importing_slots_from記錄了當前節點正在從其他節點導入的槽。importing_slots_from[i]不為null,則指向CLUSTER SETSLOT <slot> IMPORTING <source_id>
命令,<source_id>所代表的clusterNode結構。
CLUSTER SETSLOT MIGRTING 命令實現
typedef struct clusterState { // ... clusterNode *migrating_slots_to[CLUSTER_SLOTS]; } clusterState;
migrating_slots_to記錄了當前節點正在遷移至其他節點的槽。migrating_slots_to[i]不為null,則指向遷移至目標節點所代表的clusterNode結構。
ASK錯誤
在重新分片期間,源節點向目標節點遷移槽的過程中,可能屬于這個槽的一部分鍵值對一部分保存在源節點當中,而另一部分保存在目標節點當中。
客戶端向源節點發送一個與數據庫鍵有關的命令,恰好這個槽正在被遷移。
源節點現在自己的數據庫中查找指定的鍵,如果找到,直接執行。
如果沒有找到,節點會檢查migrating_slots_to[i]查看鍵是否正在遷移,如果在遷移就返回一個ask錯誤,引導客戶端轉向目標節點。
ASKING
客戶端收到ask錯誤之后,會先執行ASKING命令,再向目標節點發送命令。ASKING命令就是打開發送該命令的客戶端的REDIS_ASKING標識。一般來說客戶端發送的鍵如果不屬于自己負責會返回MOVED錯誤(槽只遷移部分,這時槽還不屬于目標節點負責),但還會檢查importing_slots_from[i],如果顯示節點正在導入槽i,并且發送命令的客戶端帶有REDIS_ASKING標識,那么它就會破例執行一次該命令。
集群的故障轉移
集群的故障轉移效果和哨兵模式類似,也是將從節點升級成主節點。舊的主節點重新上線后將會成為新主節點的從節點。
故障檢測
集群中每個節點會定期的向集群中其他節點發送PING消息,檢測對方是否在線,如果指定時間內沒有收到PONG消息,那么就將該節點標記為疑似下線。clusterState.nodes字典中找到該節點的clusterNode結構,將flags屬性修改成REDIS_NODE_PFAIL標識。
集群中各個節點會互相發送消息來交換集群中各個節點的狀態,例如:主節點A得知主節點B認為主節點C進入了疑似下線狀態,主節點A會在clusterState.nodes字典中找到節點C的clusterNode結構,并將主節點B的下線報告添加到clusterNode結構的fail_reports鏈表當中。
每一個下線報告由一個clusterNodeFailReport結構表示
typedef struct clusterNodeFailReport { struct clusterNode *node; // 最后一次收到下線報告的時間 mstime_t time; } clusterNodeFailReport;
如果一個集群當中,半數以上負責處理槽的主節點都將某個主節點X報告為疑似下線。那么這個主節點X將被標記為已下線。將主節點X標記成已下線的節點會向集群廣播一條關于主節點X的FAIL消息。所有收到這條FAIL消息的節點都會將主節點X標記成已下線。
故障轉移
當一個從節點發現自己正在復制的主節點進入了已下線狀態,從節點將開始對下線主節點進行故障轉移。
復制下線主節點的所有從節點,會有一個主節點被選中。
被選中的從節點會執行SLAVEOF no one 命令,成為新的主節點。
新的主節點會撤銷所有對已下線主節點的槽指派,并將這些槽全部指派給自己。
新的主節點向集群廣播一條PONG消息,這條PONG消息可以讓集群中的其他節點立即知道這個節點已經由從節點變成主節點。這個主節點已經接管了已下線節點負責處理的槽。
新的主節點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成。
選舉新的主節點
新的主節點通過選舉產生
集群的配置紀元是一個自增計數器,它的初始值為0。
當集群的某個節點開始一次故障轉移操作,集群的配置紀元的值加1。
對于每個配置紀元,集群里每個負責處理槽的主節點都有一次投票的機會,第一個想主節點要求投票的從節點將獲得主節點的投票。
當從節點發現自己正在復制的主節點進入已下線狀態時,從節點會向集群廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息,并具有投票權的主節點向這個從節點投票。
如果一個主節點具有投票權(它正在負責處理槽),并且這個主節點尚未投票給其他從節點,那么主節點將向要求投票的從節點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支持從節點成為新的主節點。
每個參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據自己收到了多少條這種消息來統計自己獲得了多少主節點的支持。
如果集群里有 N 個具有投票權的主節點,那么當一個從節點收集到大于等于 N / 2 + l 張支持票時,這個從節點就會當選為新的主節點。
因為在每一個配置紀元里面,每個具有投票權的主節點只能投一次票,所以如果有 N 個主節點進行投票,那么具有大于等于 N / 2 + l 張支持票的從節點只會有一個,這確保了新的主節點只會有一個。
如果在一個配置紀元里面沒有從節點能收集到足夠多的支持票,那么集群進人一個新的配置紀元,并再次進行選舉,直到選出新的主節點為止。
主節點選舉的過程和選舉領頭sentinel的過程非常相似。
主從復制數據丟失
主從復制之間是異步執行的,有可能master的部分數據還沒來得及同步到從數據庫,然后master就掛了,這時這部分未同步的數據就丟失了。
腦裂
腦裂就是說,某個master所在機器突然脫離了正常的網絡,跟其他slave機器不能連接,但是實際上master還運行著。此時哨兵可能就會認為master 宕機了,然后開啟選舉,將其他slave切換成了master,這個時候,集群里面就會有2個master,也就是所謂的腦裂。
此時雖然某個slave被切換成了master,但是可能client還沒來得及切換到新的master,還繼續向舊master的寫數據。
master再次恢復的時候,會被作為一個slave掛到新的master上去,自己的數據將會清空,重新從新的master復制數據,導致數據丟失。
減少數據丟失的配置
min-slaves-to-writ 1 min-slaves-max-lag 10
上述配置表示,如果至少有1個從服務器超過10秒沒有給自己ack消息,那么master不再執行寫請求。
當從數據庫因為網絡原因或者執行復雜度高命令阻塞導致滯后執行同步命令,導致數據同步延遲,造成了主從數據庫不一致。
感謝大家的閱讀,以上就是“如何分析redis中的高可用方案”的全部內容了,學會的朋友趕緊操作起來吧。相信億速云小編一定會給大家帶來更優質的文章。謝謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。