溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何分析redis中的高可用方案

發(fā)布時間:2022-01-17 10:37:04 來源:億速云 閱讀:148 作者:柒染 欄目:關系型數(shù)據(jù)庫

小編今天帶大家了解如何分析redis中的高可用方案,文中知識點介紹的非常詳細。覺得有幫助的朋友可以跟著小編一起瀏覽文章的內(nèi)容,希望能夠幫助更多想解決這個問題的朋友找到問題的答案,下面跟著小編一起深入學習“如何分析redis中的高可用方案”的知識吧。

主從復制

用戶可以通過SLAVEOF命令或者配置,讓一個服務器去復制另一個服務器。被復制的服務器稱為主服務器,進行復制的服務器稱為從服務器。這樣你在主服務器上增加鍵值,同時可以在從服務器上讀取?!鞠嚓P推薦:Redis視頻教程】

復制的過程又分為同步和命令傳播兩個步驟。

同步

同步將從服務器的數(shù)據(jù)庫狀態(tài)更新到主服務器當前的數(shù)據(jù)庫狀態(tài)。

客戶端向從服務器發(fā)送SLAVEOF命令時,從服務器會向主服務器發(fā)生SYNC命令進行同步,步驟如下:

  • 從服務器向主服務器發(fā)生SYNC命令。

  • 收到SYNC命令的主服務器執(zhí)行BGSAVE命令,在后臺生成一個RDB文件,并用一個緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行的所有寫命令。

  • 主服務器的BGSAVE命令執(zhí)行完畢后,主服務器將BGSAVE生成的RDB文件發(fā)送給從服務器,從服務器接收并載入這個RDB文件,將從服務器的數(shù)據(jù)庫狀態(tài)更新到主服務器執(zhí)行BGSAVE命令時的數(shù)據(jù)庫狀態(tài)

  • 主服務器將緩沖區(qū)的所有寫命令發(fā)送給從服務器,從服務器執(zhí)行這些寫命令,將數(shù)據(jù)庫狀態(tài)更新至主服務器當前數(shù)據(jù)庫狀態(tài)。

如何分析redis中的高可用方案

命令傳播

同步操作完成之后,主服務器和從服務器的數(shù)據(jù)庫狀態(tài)是一致的,但主服務器又接收到客戶端寫命令后,主從數(shù)據(jù)庫之間又產(chǎn)生了數(shù)據(jù)不一致,這時通過命令傳播達到數(shù)據(jù)庫一致。

PSYNC同步的優(yōu)化

2.8之前的同步每次都是全量同步,而如果是從服務器只是斷開連接了一會,事實上是不用從頭開始同步的,只需要將斷開連接這會的數(shù)據(jù)同步即可。所以2.8版本開始使用PSYNC來代替SYNC命令。

PSYNC分成全量同步和部分同步兩種情況,全量同步就是處理初次同步的狀態(tài),而部分同步就是處理斷線重連這種情況。

部分同步的實現(xiàn)

部分同步主要使用了以下三部分:

  • 主服務器的復制偏移量和從服務器的復制偏移量

  • 主服務器的復制積壓緩沖區(qū)

  • 服務器的運行ID

復制偏移量

主服務器的復制偏移量:主服務器每次向從服務器傳播N個字節(jié)的數(shù)據(jù)時,就將自己的復制偏移量+N從服務器的復制偏移量:從服務器每次收到主服務器傳播的N個字節(jié)數(shù)據(jù),就將自己的復制偏移量+N 如果主從服務器處于一致狀態(tài),那么它們的偏移量總是相同的,如果偏移量不相等,那么說明它們處于不一致狀態(tài)。

復制積壓緩沖區(qū)

復制積壓緩沖區(qū)由主服務器維護的一個固定長度的FIFO隊列,默認大小1MB,達到最大長度后,最先入隊的會被彈出,給新入隊的元素讓位置。
redis命令傳播的時候不但會發(fā)送給從服務器,還會發(fā)送給復制積壓緩沖區(qū)。

如何分析redis中的高可用方案

當從服務器重連上主服務器時,從服務器會通過PSYNC命令將自己的復制偏移量offset發(fā)送給主服務器,主服務器根據(jù)復制偏移量來決定使用部分同步還是全量同步。 如果offset偏移量之后的數(shù)據(jù)還在復制積壓緩沖區(qū),那么使用部分同步,反之使用全量同步。
(書上沒說是怎么判斷的,我猜測應該是拿主復制偏移量減去從復制偏移量,如果大于1MB就說明有數(shù)據(jù)不在緩沖積壓區(qū)?)

服務器的運行ID

服務器啟動時會生成一個40位隨機的字符作為服務器運行ID。

從服務器對主服務器初次復制時,主服務器會將自己的運行ID傳送給從服務器,而從服務器會將這個運行ID保存下來。從服務器斷線重連的時候,會將保存的運行ID發(fā)送過去,如果從服務器保存的運行ID和當前主服務器的運行ID相同,那么會嘗試部分同步,如果不同會執(zhí)行全量同步。

PSYNC的整體流程

如何分析redis中的高可用方案

心跳檢測

在命令傳播階段,從服務器會默認以每秒一次的頻率,向主服務器發(fā)送命令:
REPLICONF ACK <replication_offset>
其中replication_offset就是從服務器當前的復制偏移量。 發(fā)送REPLICONF ACK命令對于主從服務器有三個作用:

  • 檢測主從服務器的網(wǎng)絡連接狀態(tài)。

  • 輔助實現(xiàn)min-slaves選項。

  • 檢測命令丟失。

檢測主從服務器的網(wǎng)絡連接狀態(tài)

主從服務器可以通過發(fā)送和接收REPLICONF ACK命令來檢查兩者之間的網(wǎng)絡連接是否正常:如果主服務器超過一秒鐘沒有收到從服務器發(fā)來的REPLICONF ACK命令,那么主服務器就知道主從之間出現(xiàn)問題了。

輔助實現(xiàn)min-slaves選項

redis的min-slaves-to-writemin-slaves-max-lag兩個選項可以防止主從服務器在不安全的情況下執(zhí)行寫命令。

min-slaves-to-write 3
min-slaves-max-lag 10

如果配置如上,就表示如果從服務器的數(shù)量少于3個,或者3個從服務器的延遲都大于或等于10秒時,那么主服務器就將拒絕執(zhí)行寫命令。

檢測命令丟失

如果因為網(wǎng)絡故障,主服務器傳播給從服務器的寫命令在半路丟失,那么從服務器向主服務器發(fā)送REPLICONF ACK命令時,主服務器將發(fā)覺從服務器當前的復制偏移量少于自己的偏移量,那么主服務器可以根據(jù)從服務器的復制偏移量,在復制緩沖區(qū)當中找到從服務器缺少的數(shù)據(jù),將這些數(shù)據(jù)重寫發(fā)送給從服務器。

主從復制總結

其實主從復制就是多備份了一份數(shù)據(jù),因為即使有RDB和AOF進行持久化,但是可能主服務器上整個機器掛掉了,而主從復制可以將主從服務器部署在兩臺不同的機器上,這樣即使主服務器的機器掛掉了,也可以手動切換到從服務器繼續(xù)服務。

sentinel

主從雖然實現(xiàn)了數(shù)據(jù)的備份,但當主服務器掛掉時,需要手動的將從服務器切換成主服務器。而sentinel就可以實現(xiàn)當主服務器掛掉時,自動將從服務器切換成主服務器。

如何分析redis中的高可用方案

sentinel系統(tǒng)可以監(jiān)視所有的主從服務器,假設server1現(xiàn)在下線。當server1的下線時長超過用戶設定的下線時長上限時,sentinel系統(tǒng)就會對server1執(zhí)行故障轉移:

  • 首先sentinel系統(tǒng)會挑選server1下的其中一個從服務器,并將這個選中的從服務器升級成新的主服務器。

  • 之后,sentinel系統(tǒng)會向server1屬下的所有從服務器發(fā)送新的復制命令,讓他們成為新主服務器的從服務器。當所有從服務器復制新的主服務器時,故障轉移操作執(zhí)行完畢。

  • 另外,sentinel還會監(jiān)視已下線的server1,在它重新上線時,將它設置為新的主服務器的從服務器。

初始化sentinel狀態(tài)

struct sentinelState {
    char myid[CONFIG_RUN_ID_SIZE+1]; 
    // 當前紀元,用于實現(xiàn)故障轉移
    uint64_t current_epoch;
    // 保存了所有被這個sentinel監(jiān)視的主服務器
    // 字典的鍵是主服務器的名字
    // 字典的值是指向sentinelRedisInstance結構的指針
    dict *masters;
    // 是否進入了TILT模式
    int tilt;         
    // 目前正在執(zhí)行的腳本數(shù)量
    int running_scripts;   
    // 進入TILT模式的時間
    mstime_t tilt_start_time;   
    // 最后一次執(zhí)行時間處理器的時間
    mstime_t previous_time;     
    // 一個fifo隊列,包含了所有需要執(zhí)行的用戶腳本
    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狀態(tài)的masters屬性

masters記錄了所有被sentinel監(jiān)視的主服務器的相關信息,其中字典的鍵是被監(jiān)視服務器的名字,而值是被監(jiān)視服務器對應著sentinelRedisInstance結構。sentinelRedisInstance被sentinel服務器監(jiān)視的實例,可以是主服務器、從服務器或其他sentinel實例。

typedef struct sentinelRedisInstance {
    // 標識值,記錄實例的類型,以及該實例的當前狀態(tài)
    int flags;  
    // 實例的名字
    // 主服務器名字在配置文件中設置
    // 從服務器和sentinel名字由sentinel自動設置,格式是ip:port
    char *name; 
    // 運行id
    char *runid;   
    // 配置紀元,用于實現(xiàn)故障轉移
    uint64_t config_epoch;  
    // 實例的地址
    sentinelAddr *addr; /* Master host. */
    // 實例無響應多少毫秒之后,判斷為主觀下線
    mstime_t down_after_period; 
    // 判斷這個實例為客觀下線所需的支持投票數(shù)量
    unsigned int quorum;
    // 執(zhí)行故障轉移,可以同時對新的主服務器進行同步的從服務器數(shù)量
    int parallel_syncs; 
    // 刷新故障遷移狀態(tài)的最大時限
    mstime_t failover_timeout;  
    // 除了自己外,其他監(jiān)視主服務器的sentinel
    // 鍵是sentinel的名字,格式是ip:port
    // 值是鍵對應的sentinel的實例結構
    dict *sentinels;  
    // ...
} sentinelRedisInstance;

創(chuàng)建連向主服務器的網(wǎng)絡連接

初始化sentinel的最后一步是創(chuàng)建連向被監(jiān)視主服務器的網(wǎng)絡連接,會創(chuàng)建兩個連向主服務器的連接。

如何分析redis中的高可用方案

命令連接:專門向主服務器發(fā)送命令,并接收命令回復。
訂閱連接:專門用于訂閱主服務器的_sentinel_:hello頻道。

獲取主服務器信息

sentinel默認會每10秒,通過命令連接向被監(jiān)視的主服務器發(fā)送INFO命令,并通過回復獲取主服務器當前的信息。回復可以獲得以下信息。

  • 主服務器的run_id

  • 主服務器下所有從服務器的信息。

根據(jù)這些信息可以更新sentinelRedisInstance下的name字典和runid字段。

獲取從服務器信息

sentinel也會創(chuàng)建連接到從服務器的命令連接和訂閱連接。

如何分析redis中的高可用方案

sentinel默認會每10秒,通過命令連接向從服務器發(fā)送INFO命令,并通過回復獲取從服務器當前的信息?;貜腿缦拢?br/>

如何分析redis中的高可用方案

  • 從服務器的運行ID

  • 從服務器的角色role

  • 主服務器的ip和端口

  • 主服務器的連接狀態(tài)master_link_status

  • 從服務器的優(yōu)先級slave_priority

  • 從服務器的復制偏移變量

根據(jù)info的回復信息,sentinel可以更新從服務器的實例結構。

向主服務器和從服務器的訂閱連接發(fā)送信息

默認情況下,sentinel會每2秒一次,向被監(jiān)視的主服務器和從服務器發(fā)送命令。

如何分析redis中的高可用方案

s_ip:sentinel的ip地址
s_port:sentinel的端口號
s_runid:sentinel的運行id
s_epoch:sentinel當前的配置紀元
m_name:主服務器的名字
m_ip:主服務器的ip地址
m_port:主服務器的端口號
m_epoch:主服務器當前的配置紀元
向sentinel_:hello頻道發(fā)送信息,也會被監(jiān)視同一個服務器的其他sentinel監(jiān)聽到(包括自己)。

創(chuàng)建連向其他sentinel的命令連接

sentinel之間會互相創(chuàng)建命令連接。監(jiān)視同一個囑咐其的多個sentinel將形成相互連接的網(wǎng)絡。

如何分析redis中的高可用方案

sentinel之間不會創(chuàng)建訂閱連接。

檢測主觀下線狀態(tài)

sentinel會每秒一次向所有與它創(chuàng)建了命令連接的實例(主服務器、從服務器、其他sentinel)發(fā)送ping命令,通過實例的回復來判斷實例是否在線。
有效回復:實例返回+PONG、-LOADING、-MASTERDOWN其中一種。
無效回復:以上三種回復之外的其他回復,或者指定時長內(nèi)沒回復。
某個實例在down-after-milliseconds毫秒內(nèi),連續(xù)向sentinel返回無效回復。那么sentinel就會修改這個實例對應的實例結構,在結構的flags屬性中打開SRI_S_DOWN標識,標識該實例進入主觀下線狀態(tài)。(down-after-milliseconds可以在sentinel的配置文件中配置)

檢測客觀下線狀態(tài)

當sentinel將一個主服務器判斷為主觀下線后,為了確認這個主服務器是否真的下線,還會想其他同樣監(jiān)視這個主服務器的其他sentinel詢問,看其他sentinel是否也認為該主服務器下線了。超過一定數(shù)量就將主服務器判斷為客觀下線。

詢問其他sentinel是否同意該服務器下線

SENTINEL is-master-down-by-addr <ip><port><current_epoch><runid>

通過SENTINEL is-master-down-by-addr命令詢問,參數(shù)意義如下圖:

如何分析redis中的高可用方案

接收SENTINEL is-master-down-by-addr命令

其他sentinel接收到SENTINEL is-master-down-by-addr命令后,會根據(jù)其中主服務器的ip和端口,檢查主服務器是否下線,然后返回包含三個參數(shù)的Multi Bulk的回復。

如何分析redis中的高可用方案

sentinel統(tǒng)計其他sentinel同意主服務器已下線的數(shù)量,達到配置的數(shù)量后,則將主服務器的flags屬性的SRI_O_DOWN標識打開,表示主服務器已經(jīng)進入客觀下線狀態(tài)。

選舉領頭sentinel

當一個主服務器被判斷成客觀下線時,監(jiān)視這個下線主服務器的各個sentinel就會協(xié)商選舉一個新的領頭sentinel,由這個sentinel進行故障轉移操作。

如何分析redis中的高可用方案

確認主服務器進入客觀下線狀態(tài)后,會再次發(fā)送SENTINEL is-master-down-by-addr命令來選舉出領頭sentinel。

選舉規(guī)則

  • 監(jiān)視同一個主服務器的多個在線sentinel中每一個都可能成為領頭sentinel。

  • 每次進行領頭sentinel選舉之后,無論選舉是否成功,所有sentinel的配置紀元(configuration epoch)的值都會自增一次。(配置紀元,其實就是一個計數(shù)器)

  • 在一個配置紀元里,所有sentinel都有將某個sentinel設置成局部sentinel的機會,一旦設置在這個配置紀元里就不能再更改。

  • 所有發(fā)現(xiàn)主服務器客觀下線的sentinel都會要求其他sentinel將自己設置為局部領頭sentinel,也就是都會發(fā)送SENTINEL is-master-down-by-addr命令,嘗試讓其他sentinel將自己設置成局部領頭sentinel。

  • 當一個sentinel向另一個sentinel發(fā)送SENTINEL is-master-down-by-addr命令時,如果runid參數(shù)的值不是*,而是源sentinel的runid,就表示要目標sentinel將自己設置成領頭sentinel。

  • sentinel設置局部領頭的規(guī)則是先到先得,第一個設置為局部領頭sentinel后,其他的請求都被拒絕。

  • 目標sentinel在接收到一條SENTINEL is-master-down-by-addr命令后,將向源sentinel返回一個命令回復。回復中l(wèi)eader_runid參數(shù)和leader_epoch參數(shù)分別記錄了目標sentinel的局部領頭sentinel的runid和配置紀元。

  • 源sentinel接收到回復之后,會比較返回的配置紀元是否和自己的配置紀元相同,如果一樣再繼續(xù)比較返回的局部領頭sentinel的runid是否和自己的runid相同,如果一致就表示目標sentinel將自己設置成了局部領頭sentinel。

  • 如果某個sentinel被半數(shù)以上的sentinel設置成了局部領頭sentinel,那么它就成為領頭sentinel。

  • 領頭sentinel需要半數(shù)以上支持,并且每個配置紀元內(nèi)只能設置一次,那么一個配置紀元里,只會出現(xiàn)一個領頭sentinel

  • 如果在一定時限內(nèi),每一個sentinel被選舉成領頭sentinel(沒人沒獲取半數(shù)以上選票),那么各個sentinel在一段時間之后再次選舉,直到選出領頭sentinel

故障轉移

故障轉移包含以下三個步驟:

  • 在已下線的主服務器下所有從服務器里,挑選出一個從服務器轉換成主服務器。

  • 讓已下線的主服務器屬下的所有從服務器改為復制新的主服務器。

  • 將已經(jīng)下線的主服務器設置為新服務器的從服務器,舊的主服務器重新上線后,它就成為新的主服務器的從服務器。

選出新的主服務器

已下線的主服務器下所有從服務器里,挑選出一個從服務器,向這個從服務器發(fā)送SLAVEOF no one命令,將這個從服務器轉換成主服務器。

挑選新主服務器的規(guī)則

領頭的sentinel會將已下線主服務器的所有從服務器保存到一個列表里面,然后對這個列表進行過濾,挑選出新的主服務器。

  • 刪除列表中所有處于下線或者斷線狀態(tài)的從服務器。

  • 刪除列表中所有最近五秒內(nèi)沒有回復過領頭sentinel的INFO命令的從服務器

  • 刪除所有與已下線服務器連接斷開超過 dwon-after-milliseconds * 10毫秒的服務器

  • 然后根據(jù)從服務器的優(yōu)先級,對列表中剩余的從服務器進行排序,并選出其中優(yōu)先級最高的服務器。

  • 如果有多個相同最高優(yōu)先級的從服務器,那么就根據(jù)復制偏移量進行排序,選出最大偏移量的從服務器(復制偏移量最大也代表它保存的數(shù)據(jù)最新)

  • 如果復制偏移量也相同,那么就根據(jù)runid進行排序,選其中runid最小的從服務器

發(fā)送slaveof no one 命令之后,領頭sentinel會每秒一次向被升級的從服務器發(fā)送info命令(平常是每10秒一次),如果返回的回復role從原來的slave變成了master,那么領頭sentinel就知道從服務器已經(jīng)升級成主服務器了。

修改從服務器的復制目標

通過SLAVEOF命令來使從服務器復制新的主服務器。當sentinel監(jiān)測到舊的主服務器重新上線后,也會發(fā)送SLAVEOF命令使它成為新的主服務器的從服務器。

sentinel總結

sentinel其實就是一個監(jiān)控系統(tǒng),,而sentinel監(jiān)測到主服務器下線后,可以通過選舉機制選出一個領頭的sentinel,然后由這個領頭的sentinel將下線主服務器下的從服務器挑選一個切換成主服務器,而不用人工手動切換。

集群

哨兵模式雖然做到了主從自動切換,但是還是只有一臺主服務器進行寫操作(當然哨兵模式也可以監(jiān)視多個主服務器,但需要客戶端自己實現(xiàn)負載均衡)。官方也提供了自己的方式實現(xiàn)集群。

節(jié)點

每個redis服務實例就是一個節(jié)點,多個連接的節(jié)點組成一個集群。

CLUSTER MEET <ip><port>

向另一個節(jié)點發(fā)送CLUSTER MEET命令,可以讓節(jié)點與目標節(jié)點進行握手,握手成功就能將該節(jié)點加入到當前集群。

啟動節(jié)點

redis服務器啟動時會根據(jù)cluster-enabled配置選項是否為yes來決定是否開啟服務器集群模式。

如何分析redis中的高可用方案

集群數(shù)據(jù)結構

每個節(jié)點都會使用一個clusterNode結構記錄自己的狀態(tài),并為集群中其他節(jié)點都創(chuàng)建一個相應的clusterNode結構,記錄其他節(jié)點狀態(tài)。

typedef struct clusterNode {
    // 創(chuàng)建節(jié)點的時間
    mstime_t ctime; 
    // 節(jié)點的名稱
    char name[CLUSTER_NAMELEN];
    // 節(jié)點標識
    // 各種不同的標識值記錄節(jié)點的角色(比如主節(jié)點或從節(jié)點)
    // 以及節(jié)點目前所處的狀態(tài)(在線或者下線)
    int flags;     
    // 節(jié)點當前的配置紀元,用于實現(xiàn)故障轉移
    uint64_t configEpoch;
    // 節(jié)點的ip地址
    char ip[NET_IP_STR_LEN];  
    // 保存建立連接節(jié)點的有關信息
    clusterLink *link;          
    
    list *fail_reports;  
    // ...
} clusterNode;

clusterLink保存著連接節(jié)點所需的相關信息

typedef struct clusterLink {
    // ...
    // 連接的創(chuàng)建時間
    mstime_t ctime;           
    // 與這個連接相關聯(lián)的節(jié)點,沒有就為null
    struct clusterNode *node;   
    // ...
} clusterLink;

每個節(jié)點還保存著一個clusterState結構,它記錄了在當前節(jié)點視角下,集群目前所處的狀態(tài),例如集群在線還是下線,集群包含多少個節(jié)點等等。

typedef struct clusterState {
    // 指向當前節(jié)點clusterNode的指針
    clusterNode *myself;  
    // 集群當前的配置紀元,用于實現(xiàn)故障轉移
    uint64_t currentEpoch;
    // 集群當前的狀態(tài),上線或者下線
    int state;           
    // 集群中至少處理一個槽的節(jié)點數(shù)量
    int size;      
    // 集群節(jié)點的名單(包括myself節(jié)點)
    // 字典的鍵是節(jié)點的名字,字典的值為節(jié)點對應的clusterNode結構
    dict *nodes; 
} clusterState;

CLUSTER MEET 命令的實現(xiàn)

CLUSTER MEET <ip><port>

  • 節(jié)點 A 會為節(jié)點 B 創(chuàng)建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes 字典里面。

  • 之后,節(jié)點 A 將根據(jù) CLUSTER MEET 命令給定的 IP 地址和端口號,向節(jié)點 B 發(fā)送一條 MEET 消息。

  • 如果一切順利,節(jié)點 B 將接收到節(jié)點 A 發(fā)送的 MEET 消息,節(jié)點 B 會為節(jié)點 A 創(chuàng)建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes字典里面。

  • 之后,節(jié)點 B 將向節(jié)點 A 返回一條 PONG 消息。

  • 如果一切順利,節(jié)點 A 將接收到節(jié)點 B 返回的 PONG 消息,通過這條 PONG 消息節(jié)點 A 可以知道節(jié)點 B 已經(jīng)成功地接收到了自己發(fā)送的 MEET 消息。

  • 之后,節(jié)點 A 將向節(jié)點 B 返回一條 PING 消息。

  • 如果一切順利,節(jié)點B將接收到節(jié)點A返回的PING消息,通過這條PING消息節(jié)點B知道節(jié)點A已經(jīng)成功接收到自己返回的PONG消息,握手完成。

如何分析redis中的高可用方案

槽指派

集群的整個數(shù)據(jù)庫被分為16384個槽,每個鍵都屬于16384個槽的其中一個,集群中每個節(jié)點處理0個或16384個槽。當所有的槽都有節(jié)點在處理時,集群處于上線狀態(tài),否則就是下線狀態(tài)。

CLUSTER ADDSLOTS

CLUSTER ADDSLOTS <slot>...
通過CLUSTER ADDSLOTS命令可以將指定槽指派給當前節(jié)點負責,例如:CLUSTER ADDSLOTS 0 1 2 3 4 可以將0至4的槽指派給當前節(jié)點

記錄節(jié)點的槽指派信息

clusterNode結構的slots屬性和numslot屬性記錄了節(jié)點負責處理哪些槽:

typedef struct clusterNode {
         
    unsigned char slots[CLUSTER_SLOTS/8];
    
    int numslots;
    // ...
} clusterNode;

slots:是一個二進制數(shù)組,一共包含16384個二進制位。當二進制位的值是1,代表節(jié)點負責處理該槽,如果是0,代表節(jié)點不處理該槽numslots:numslots屬性則記錄節(jié)點負責處理槽的數(shù)量,也就是slots中值為1的二進制位的數(shù)量。

傳播節(jié)點的槽指派信息

節(jié)點除了會將自己負責的槽記錄在clusterNode中,還會將slots數(shù)組發(fā)送給集群中的其他節(jié)點,以此告知其他節(jié)點自己目前負責處理哪些槽。

typedef struct clusterState {
    clusterNode *slots[CLUSTER_SLOTS];
} clusterState;

slots包含16384個項,每一個數(shù)組項都是指向clusterNode的指針,表示被指派給該節(jié)點,如果未指派給任何節(jié)點,那么指針指向NULL。

CLUSTER ADDSLOTS命令的實現(xiàn)

如何分析redis中的高可用方案

在集群中執(zhí)行命令

客戶端向節(jié)點發(fā)送與數(shù)據(jù)庫有關的命令時,接收命令的節(jié)點會計算出命令要處理的數(shù)據(jù)庫鍵屬于哪個槽,并檢查該槽是否指派給了自己。
如果指派給了自己,那么該節(jié)點直接執(zhí)行該命令。如果沒有,那么該節(jié)點會向客戶端返回一個MOCED的錯誤,指引客戶端轉向正確的節(jié)點,并再次發(fā)送執(zhí)行的命令。

如何分析redis中的高可用方案

計算鍵屬于那個槽

如何分析redis中的高可用方案

CRC16(key)是計算出鍵key的CRC16的校驗和,而 & 16383就是取余,算出0-16383之間的整數(shù)作為鍵的槽號。

判斷槽是否由當前節(jié)點負責處理

計算出鍵所屬的槽號i后,節(jié)點就能判斷該槽號是否由自己處理。
如果clusterState.slots[i]等于如果clusterState.myself,那么由自己負責該節(jié)點可以直接執(zhí)行命令。
如果不相等,那么可以獲取clusterState.slots[i]指向如果clusterNode的ip和端口,向客戶端返回MOVED錯誤,指引客戶端轉向負責該槽的節(jié)點。

集群模式下不會打印MOVED錯誤,而是直接自動轉向。

重新分片

redis集群重新分配可以將任意數(shù)量已經(jīng)指派給某個節(jié)點的槽改為指派給另一個節(jié)點,相關槽所屬的鍵值對也會從源節(jié)點移動到目標節(jié)點。
重新分片操作是在線進行的,在重新分片的過程中,集群不用下線,源節(jié)點和目標節(jié)點都可以繼續(xù)處理命令請求。 redis集群的重新分片操作是由redis-trib負責執(zhí)行。重新分片執(zhí)行步驟如下:

  • redis-trib對目標節(jié)點發(fā)送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,讓目標節(jié)點準備好從源節(jié)點導入槽slot的鍵值對。

  • redis-trib對源節(jié)點發(fā)送CLUSTER SETSLOT <slot> MIGRTING <target_id>命令,讓源節(jié)點準備好將屬于槽slot的鍵值對遷移至目標節(jié)點。

  • redis-trib向源節(jié)點發(fā)送CLUSTER GETKEYSINSLOT <slot> <count>命令,獲取最多count個屬于槽的鍵值對的鍵名稱。

  • 對于步驟3獲取的每個鍵名,redis-trib都向源節(jié)點發(fā)送一個MIGRTING <target_ip> <target_port> <key_name> 0 <timeout>命令,將被選中的鍵值對從源節(jié)點遷移至目標節(jié)點。

  • 重復執(zhí)行步驟3和步驟4,直到源節(jié)點保存的所以屬于槽slot的鍵值對都被遷移至目標節(jié)點。

  • redis-trib向集群中任何一個節(jié)點發(fā)送CLUSTER SETSLOT <slot> NODE <target_id>命令,將槽指派給目標節(jié)點。這一信息最終會通過消息發(fā)送至整個集群。

如何分析redis中的高可用方案

CLUSTER SETSLOT IMPORTING 命令實現(xiàn)

typedef struct clusterState {
    // ...
    clusterNode *importing_slots_from[CLUSTER_SLOTS];

} clusterState;

importing_slots_from記錄了當前節(jié)點正在從其他節(jié)點導入的槽。importing_slots_from[i]不為null,則指向CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,<source_id>所代表的clusterNode結構。

CLUSTER SETSLOT MIGRTING 命令實現(xiàn)

typedef struct clusterState {
    // ...
    clusterNode *migrating_slots_to[CLUSTER_SLOTS];

} clusterState;

migrating_slots_to記錄了當前節(jié)點正在遷移至其他節(jié)點的槽。migrating_slots_to[i]不為null,則指向遷移至目標節(jié)點所代表的clusterNode結構。

ASK錯誤

在重新分片期間,源節(jié)點向目標節(jié)點遷移槽的過程中,可能屬于這個槽的一部分鍵值對一部分保存在源節(jié)點當中,而另一部分保存在目標節(jié)點當中。
客戶端向源節(jié)點發(fā)送一個與數(shù)據(jù)庫鍵有關的命令,恰好這個槽正在被遷移。
源節(jié)點現(xiàn)在自己的數(shù)據(jù)庫中查找指定的鍵,如果找到,直接執(zhí)行。
如果沒有找到,節(jié)點會檢查migrating_slots_to[i]查看鍵是否正在遷移,如果在遷移就返回一個ask錯誤,引導客戶端轉向目標節(jié)點。

ASKING

客戶端收到ask錯誤之后,會先執(zhí)行ASKING命令,再向目標節(jié)點發(fā)送命令。ASKING命令就是打開發(fā)送該命令的客戶端的REDIS_ASKING標識。一般來說客戶端發(fā)送的鍵如果不屬于自己負責會返回MOVED錯誤(槽只遷移部分,這時槽還不屬于目標節(jié)點負責),但還會檢查importing_slots_from[i],如果顯示節(jié)點正在導入槽i,并且發(fā)送命令的客戶端帶有REDIS_ASKING標識,那么它就會破例執(zhí)行一次該命令。

如何分析redis中的高可用方案

集群的故障轉移

集群的故障轉移效果和哨兵模式類似,也是將從節(jié)點升級成主節(jié)點。舊的主節(jié)點重新上線后將會成為新主節(jié)點的從節(jié)點。

故障檢測

集群中每個節(jié)點會定期的向集群中其他節(jié)點發(fā)送PING消息,檢測對方是否在線,如果指定時間內(nèi)沒有收到PONG消息,那么就將該節(jié)點標記為疑似下線。clusterState.nodes字典中找到該節(jié)點的clusterNode結構,將flags屬性修改成REDIS_NODE_PFAIL標識。
集群中各個節(jié)點會互相發(fā)送消息來交換集群中各個節(jié)點的狀態(tài),例如:主節(jié)點A得知主節(jié)點B認為主節(jié)點C進入了疑似下線狀態(tài),主節(jié)點A會在clusterState.nodes字典中找到節(jié)點C的clusterNode結構,并將主節(jié)點B的下線報告添加到clusterNode結構的fail_reports鏈表當中。
每一個下線報告由一個clusterNodeFailReport結構表示

typedef struct clusterNodeFailReport {
    struct clusterNode *node; 
    // 最后一次收到下線報告的時間
    mstime_t time;            
} clusterNodeFailReport;

如果一個集群當中,半數(shù)以上負責處理槽的主節(jié)點都將某個主節(jié)點X報告為疑似下線。那么這個主節(jié)點X將被標記為已下線。將主節(jié)點X標記成已下線的節(jié)點會向集群廣播一條關于主節(jié)點X的FAIL消息。所有收到這條FAIL消息的節(jié)點都會將主節(jié)點X標記成已下線。

故障轉移

當一個從節(jié)點發(fā)現(xiàn)自己正在復制的主節(jié)點進入了已下線狀態(tài),從節(jié)點將開始對下線主節(jié)點進行故障轉移。

  • 復制下線主節(jié)點的所有從節(jié)點,會有一個主節(jié)點被選中。

  • 被選中的從節(jié)點會執(zhí)行SLAVEOF no one 命令,成為新的主節(jié)點。

  • 新的主節(jié)點會撤銷所有對已下線主節(jié)點的槽指派,并將這些槽全部指派給自己。

  • 新的主節(jié)點向集群廣播一條PONG消息,這條PONG消息可以讓集群中的其他節(jié)點立即知道這個節(jié)點已經(jīng)由從節(jié)點變成主節(jié)點。這個主節(jié)點已經(jīng)接管了已下線節(jié)點負責處理的槽。

  • 新的主節(jié)點開始接收和自己負責處理的槽有關的命令請求,故障轉移完成。

選舉新的主節(jié)點

新的主節(jié)點通過選舉產(chǎn)生

  • 集群的配置紀元是一個自增計數(shù)器,它的初始值為0。

  • 當集群的某個節(jié)點開始一次故障轉移操作,集群的配置紀元的值加1。

  • 對于每個配置紀元,集群里每個負責處理槽的主節(jié)點都有一次投票的機會,第一個想主節(jié)點要求投票的從節(jié)點將獲得主節(jié)點的投票。

  • 當從節(jié)點發(fā)現(xiàn)自己正在復制的主節(jié)點進入已下線狀態(tài)時,從節(jié)點會向集群廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息,并具有投票權的主節(jié)點向這個從節(jié)點投票。

  • 如果一個主節(jié)點具有投票權(它正在負責處理槽),并且這個主節(jié)點尚未投票給其他從節(jié)點,那么主節(jié)點將向要求投票的從節(jié)點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節(jié)點支持從節(jié)點成為新的主節(jié)點。

  • 每個參與選舉的從節(jié)點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據(jù)自己收到了多少條這種消息來統(tǒng)計自己獲得了多少主節(jié)點的支持。

  • 如果集群里有 N 個具有投票權的主節(jié)點,那么當一個從節(jié)點收集到大于等于 N / 2 + l 張支持票時,這個從節(jié)點就會當選為新的主節(jié)點。

  • 因為在每一個配置紀元里面,每個具有投票權的主節(jié)點只能投一次票,所以如果有 N 個主節(jié)點進行投票,那么具有大于等于 N / 2 + l 張支持票的從節(jié)點只會有一個,這確保了新的主節(jié)點只會有一個。

  • 如果在一個配置紀元里面沒有從節(jié)點能收集到足夠多的支持票,那么集群進人一個新的配置紀元,并再次進行選舉,直到選出新的主節(jié)點為止。

主節(jié)點選舉的過程和選舉領頭sentinel的過程非常相似。

數(shù)據(jù)丟失

主從復制數(shù)據(jù)丟失

主從復制之間是異步執(zhí)行的,有可能master的部分數(shù)據(jù)還沒來得及同步到從數(shù)據(jù)庫,然后master就掛了,這時這部分未同步的數(shù)據(jù)就丟失了。

腦裂

腦裂就是說,某個master所在機器突然脫離了正常的網(wǎng)絡,跟其他slave機器不能連接,但是實際上master還運行著。此時哨兵可能就會認為master 宕機了,然后開啟選舉,將其他slave切換成了master,這個時候,集群里面就會有2個master,也就是所謂的腦裂。
此時雖然某個slave被切換成了master,但是可能client還沒來得及切換到新的master,還繼續(xù)向舊master的寫數(shù)據(jù)。
master再次恢復的時候,會被作為一個slave掛到新的master上去,自己的數(shù)據(jù)將會清空,重新從新的master復制數(shù)據(jù),導致數(shù)據(jù)丟失。

減少數(shù)據(jù)丟失的配置

min-slaves-to-writ 1
min-slaves-max-lag 10

上述配置表示,如果至少有1個從服務器超過10秒沒有給自己ack消息,那么master不再執(zhí)行寫請求。

主從數(shù)據(jù)不一致

當從數(shù)據(jù)庫因為網(wǎng)絡原因或者執(zhí)行復雜度高命令阻塞導致滯后執(zhí)行同步命令,導致數(shù)據(jù)同步延遲,造成了主從數(shù)據(jù)庫不一致。

感謝大家的閱讀,以上就是“如何分析redis中的高可用方案”的全部內(nèi)容了,學會的朋友趕緊操作起來吧。相信億速云小編一定會給大家?guī)砀鼉?yōu)質的文章。謝謝大家對億速云網(wǎng)站的支持!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI