Redis 集羣

 

 

集羣的概念早在 Redis 3.0 以前討論了,3.0 纔在源碼中出現。Redis 集羣要考慮的問題:node

  1. 節點之間怎麼據的同步,如何作到數據一致性。一主一備的模式,能夠用 Redis 內部實現的主從備份實現數據同步。但節點不斷增多,存在多個 master 的時候,同步的難度會越大。
  2. 如何作到負載均衡?請求量大的時候,如何將請求儘可能均分到各個服務器節點,負載均衡算法作的很差會致使雪崩。
  3. 如何作到平滑拓展?當業務量增長的時候,可否經過簡單的配置即讓新的 Redis 節點變爲可用。
  4. 可用性如何?當某些節點鼓掌,可否快速恢復服務器集羣的工做能力。
  5. ……

一個穩健的後臺系統須要太多的考慮。redis

一致性哈希算法(consistent hashing)

背景

一般,業務量較大的時候,考慮到性能的問題(索引速度慢和訪問量過大),不會把全部的數據存放在一個 Redis 服務器上。這裏須要將一堆的鍵值均分存儲到多個 Redis 服務器,能夠經過:算法

target = hash(key)\%N,其中 target 爲目標節點,key 爲鍵,N 爲 Redis 節點的個數哈希取餘的方式會將不一樣的 key 分發到不一樣的服務器上。數據庫

但考慮以下場景:後端

  1. 業務量忽然增長,現有服務器不夠用。增長服務器節點後,依然經過上面的計算方式:hash(key)%(N+1) 作數據分片和分發,但以前的 key 會被分發到與以前不一樣的服務器上,致使大量的數據失效,須要從新寫入(set)Redis 服務器。
  2. 其中的一個服務器掛了。若是不作及時的修復,大量被分發到此服務器請求都會失效。

這也是兩個問題。數組

一致性哈希算法

設定一個圓環上 0 23̂2-1 的點,每一個點對應一個緩存區,每一個鍵值對存儲的位置也經哈希計算後對應到環上節點。但現實中不可能有如此多的節點,因此假若鍵值對經哈希計算後對應的位置沒有節點,那麼順時針找一個節點存儲它。緩存

考慮增長服務器節點的狀況,該節點順時針方向的數據仍然被存儲到順時針方向的節點上,但它逆時針方向的數據被存儲到它本身。這時候只有部分數據會失效,被映射到新的緩存區。服務器

考慮節點減小的狀況。該缺失節點順時針方向上的數據仍然被存儲到其順時針方向上的節點,設爲 beta,其逆時針方向上的數據會被存儲到 beta 上。一樣,只有有部分數據失效,被從新映射到新的服務器節點。網絡

這種狀況比較麻煩,上面圖中 gamma 節點失效後,會有大量數據映射到 alpha 節點,最怕 alpha 扛不住,接下去 beta 也扛不住,這就是多米諾骨牌效應;)。這裏涉及到數據平衡性和負載均衡的話題。數據平衡性是說,數據儘量均分到每一個節點上去,存儲達到均衡。數據結構

虛擬節點簡介

將多個虛擬節點對應到一個真實的節點,存儲能夠達到更均衡的效果。以前的映射方案爲:

key -> node

中間多了一個層虛擬節點後,多了一層映射關係:

key -> <virtual node> -> node

爲何須要虛擬節點

虛擬節點的設計有什麼好處?假設有四個節點以下:

節點 3 忽然宕機,這時候本來在節點 3 的數據,會被定向到節點 4。在三個節點中節點 4 的請求量是最大的。這就致使節點與節點之間請求量是不均衡的。

爲了達到節點與節點之間請求訪問的均衡,嘗試將原有節點 3 的數據平均定向到到節點 1,2,4. 如此達到負載均衡的效果,以下:

總之,一致性哈希算法是但願在增刪節點的時候,讓儘量多的緩存數據不失效。

怎麼實現?

一致性哈希算法,既能夠在客戶端實現,也能夠在中間件上實現(如 proxy)。在客戶端實現中,當客戶端初始化的時候,須要初始化一張預備的 Redis 節點的映射表:hash(key)=> . 這有一個缺點,假設有多個客戶端,當映射表發生變化的時候,多個客戶端須要同時拉取新的映射表。

另外一個種是中間件(proxy)的實現方法,即在客戶端和 Redis 節點之間加多一個代理,代理通過哈希計算後將對應某個 key 的請求分發到對應的節點,一致性哈希算法就在中間件裏面實現。能夠發現,twemproxy 就是這麼作的。

twemproxy - Redis 集羣管理方案

twemproxy 是 twitter 開源的一個輕量級的後端代理,兼容 redis/memcache 協議,可用以管理 redis/memcache 集羣。

twemproxy 內部有實現一致性哈希算法,對於客戶端而言,twemproxy 至關因而緩存數據庫的入口,它無需知道後端的部署是怎樣的。twemproxy 會檢測與每一個節點的鏈接是否健康,出現異常的節點會被剔除;待一段時間後,twemproxy 會再次嘗試鏈接被剔除的節點。

一般,一個 Redis 節點池能夠分由多個 twemproxy 管理,少數 twemproxy 負責寫,多數負責讀。twemproxy 能夠實時獲取節點池內的全部 Redis 節點的狀態,但其對故障修復的支持還有待提升。解決的方法是能夠藉助 redis sentinel 來實現自動的主從切換,當主機 down 掉後,sentinel 會自動將從機配置爲主機。而 twemproxy 能夠定時向 redis sentinel 拉取信息,從而替換出現異常的節點。

twemproxy 的更多細節,這裏再也不作深刻的討論。

 

Redis 官方版本支持的集羣

最新版本的 Redis 也開始支持集羣特性了,不再用靠着外援過日子了。基本的思想是,集羣裏的每一個 Redis 都只存儲必定的鍵值對,這個「必定」能夠經過默認或自定義的哈希函數來決定,當一個 Redis 收到請求後,會首先查看此鍵值對是否該由本身來處理,是則繼續往下執行;不然會產生一個相似於 http 3XX 的重定向,要求客戶端去請求集羣中的另外一個 Redis。

Redis 每個實例都會經過遵照必定的協議來維護這個集羣的可用性,穩定性。有興趣可前往官網瞭解 Redis 集羣的實現細則。

 

redis cluster 就是想要讓一羣的節點實現自治,有自我修復的功能,數據分片和負載均衡。

數據結構

基本上集羣中的每個節點都須要知道其餘節點的狀況,從而,若是網絡中有五個節點就下面的圖:

其中每條線都表明雙向聯通。特別的,若是 redis master 還配備了 replica,圖畫起來會稍微複雜一點。

redis cluster 中有幾個比較重要的數據結構,一個用以描述節點 struct clusterNode,一個用以描述集羣的情況 struct clusterState。

節點的信息包括:自己的一些屬性,還有它的主從節點,心跳和主從複製信息,和與該節點的鏈接上下文。

typedef struct clusterNode {
    mstime_t ctime; /* Node object creation time. */
    char name[REDIS_CLUSTER_NAMELEN]; /* Node name, hex string, sha1-size */
    int flags; /* REDIS_NODE_... */
    uint64_t configEpoch; /* Last configEpoch observed for this node */
    // 該節點會處理的slot
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots; /* Number of slots handled by this node */
    // 從機信息
    int numslaves; /* Number of slave nodes, if this is a master */
    // 從機節點數組
    struct clusterNode **slaves; /* pointers to slave nodes */
    // 主機節點數組
    struct clusterNode *slaveof; /* pointer to the master node */
    // 一些有用的時間點
    mstime_t ping_sent; /* Unix time we sent latest ping */
    mstime_t pong_received; /* Unix time we received the pong */
    mstime_t fail_time; /* Unix time when FAIL flag was set */
    mstime_t voted_time; /* Last time we voted for a slave of this master */
    mstime_t repl_offset_time; /* Unix time we received offset for this node */
    long long repl_offset; /* Last known repl offset for this node. */
    // 最近被記錄的地址和端口
    char ip[REDIS_IP_STR_LEN]; /* Latest known IP address of this node */
    int port; /* Latest known port of this node */
    // 與該節點的鏈接上下文
    clusterLink *link; /* TCP/IP link with this node */
    list *fail_reports; /* List of nodes signaling this as failing */
    } clusterNode;
    集羣的狀態包括下面的信息:
typedef struct clusterState {
    clusterNode *myself; /* This node */
    // 配置版本
    uint64_t currentEpoch;
    // 集羣的狀態
    int state; /* REDIS_CLUSTER_OK, REDIS_CLUSTER_FAIL, ... */
    // 存儲全部節點的哈希表
    int size; /* Num of master nodes with at least one slot */
    dict *nodes; /* Hash table of name -> clusterNode structures */
    // 黑名單節點,一段時間內不會再加入到集羣中
    dict *nodes_black_list; /* Nodes we don't re-add for a few seconds. */
    // slot 數據正在遷移到migrating_slots_to[slot] 節點
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
    // slot 數據正在從importing_slots_from[slot] 遷移到本機
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
    // slot 數據由slots[slot] 節點來處理
    clusterNode *slots[REDIS_CLUSTER_SLOTS];
    // slot 到key 的一個映射
    zskiplist *slots_to_keys;
    // 記錄了故障修復的信息
    /* The following fields are used to take the slave state on elections. */
    mstime_t failover_auth_time; /* Time of previous or next election. */
    int failover_auth_count; /* Number of votes received so far. */
    int failover_auth_sent; /* True if we already asked for votes. */
    int failover_auth_rank; /* This slave rank for current auth request. */
    uint64_t failover_auth_epoch; /* Epoch of the current election. */
    int cant_failover_reason; /* Why a slave is currently not able to
    failover. See the CANT_FAILOVER_* macros. */
    // 人工故障修復的一些信息
    ......
} clusterState;

 

在正常的 Redis 集羣中的任何一個節點都能感知到其餘節點。

上面頻繁出現 slot 單詞。以前咱們學哈希表的時候,能夠把 slot 理解爲哈希表中的桶(bucket)。爲何須要slot?這和 redis cluster 的數據分區和訪問有關。建議大概看完 Redis 對數據結構後,接着看 clusterCommand() 這個函數,由此知道 redis cluster 能提供哪些服務和功能。接着往下看。

數據訪問

在 http 有 301 狀態碼:301 Moved Permanently,它表示用戶所要訪問的內容已經遷移到一個地址了,須要向新的地址發出請求。redis cluster 很明顯也是這麼作的。在前面講到,redis cluster 中的每個節點都須要知道其餘節點的狀況,這裏就包括其餘節點負責處理哪些鍵值對。

在主函數中,Redis 會檢測在啓用集羣模式的狀況下,會檢測命令中指定的 key 是否該由本身來處理,若是不是的話,會返回一個相似於重定向的錯誤返回到客戶端。而「是否由本身來處理」就是看 hash(key) 值是否落在本身所負責的 slot 中。

 

typedef struct clusterNode {
    ......
    // 該節點會處理的slot
    unsigned char slots[REDIS_CLUSTER_SLOTS/8]; /* slots handled by this node */
    int numslots; /* Number of slots handled by this node */
    ......
} clusterNode;

可能會有疑問:這樣的數據訪問機制在不是會浪費一個請求嗎?確實,若是直接向集羣中的節點盲目訪問一個 key 的話,確實須要發起兩個請求。爲此,redis cluster 配備了 slot 表,用戶經過 slots 命令先向集羣請求這個 slot 表,獲得這個表能夠獲取哪些節點負責哪些 slot,繼而客戶端能夠訪問再訪問集羣中的數據。這樣,就能夠在大多數的場景下節省一個請求,直達目標節點。固然,這個 slot 表是隨時出現變動的,因此客戶端不可以一 本萬利一直使用這個 slot 表,能夠實現一個定時器,超時後再向集羣節點獲取 slot 表。

你能夠閱讀 getNodeByQuery(),流程不難。

新的節點

Redis 剛剛啓動時候會檢測集羣配置文件中是否有預配置好的節點,若是有的話,會添加到節點哈希表中,在適當的時候鏈接這個節點,並和它打招呼–握手。

// 加載集羣配置文件
int clusterLoadConfig(char *filename) {
    ......
    // 若是該節點不在哈希表中,會添加
    /* Create this node if it does not exist */
    n = clusterLookupNode(argv[0]);
    if (!n) {
        n = createClusterNode(argv[0],0);
        clusterAddNode(n);
    }
    /* Address and port */
    if ((p = strrchr(argv[1],':')) == NULL) goto fmterr;
    *p = '\0';
    memcpy(n->ip,argv[1],strlen(argv[1])+1);
    n->port = atoi(p+1);
    ......
}

注意,加載配置文件後,不會當即進入握手階段。 另外兩個新增節點的時機,當其餘節點向該節點打招呼時候,該節點會記錄下對端節點,以及對端所知悉的節點;Redis 管理人員告知,Redis 管理人員能夠經過普通的 redis meet 命令,至關因而人工將某個節點加入到集羣中。

 

當和其餘節點開始握手時,會調用 clusterStartHandshake(),它只會初始化握手的初始信息,並不會馬上向其餘節點發起握手,:按照 Redis 的習慣是在集羣定時處理函數 clusterCron() 中。

int clusterStartHandshake(char *ip, int port) {
    clusterNode *n;
    char norm_ip[REDIS_IP_STR_LEN];
    struct sockaddr_storage sa;
    // 分IPV4 和IPV6 兩種狀況
    /* IP sanity check */
    if (inet_pton(AF_INET,ip,
        &(((struct sockaddr_in *)&sa)->sin_addr)))
    {
        sa.ss_family = AF_INET;
    } else if (inet_pton(AF_INET6,ip,
        &(((struct sockaddr_in6 *)&sa)->sin6_addr)))
    {
        sa.ss_family = AF_INET6;
    } else {
        errno = EINVAL;
        return 0;
    }
    // 端口有效性檢測
    /* Port sanity check */
    if (port <= 0 || port > (65535-REDIS_CLUSTER_PORT_INCR)) {
        errno = EINVAL;
        return 0;
    }
    // 標準化ip
    /* Set norm_ip as the normalized string representation of the node
    * IP address. */
    memset(norm_ip,0,REDIS_IP_STR_LEN);
    if (sa.ss_family == AF_INET)
        inet_ntop(AF_INET,
            (void*)&(((struct sockaddr_in *)&sa)->sin_addr),
            norm_ip,REDIS_IP_STR_LEN);
    else
        inet_ntop(AF_INET6,
            (void*)&(((struct sockaddr_in6 *)&sa)->sin6_addr),
            norm_ip,REDIS_IP_STR_LEN);
    // 若是這個節點正在握手狀態,則不須要重複進入,直接退出
    if (clusterHandshakeInProgress(norm_ip,port)) {
        errno = EAGAIN;
        return 0;
    }
    // 建立一個新的節點,並加入到節點哈希表中
    /* Add the node with a random address (NULL as first argument to
    * createClusterNode()). Everything will be fixed during the
    * handshake. */
    n = createClusterNode(NULL,REDIS_NODE_HANDSHAKE|REDIS_NODE_MEET);
    memcpy(n->ip,norm_ip,sizeof(n->ip));
    n->port = port;
    clusterAddNode(n);
    return 1;
}

其餘節點收到後在 clusterProcessGossipSection() 中將新的節點添加到哈希表中。

心跳機制

仍是那句話,redis cluster 中的每個節點都須要知道其餘節點的狀況。要達到這個目標,必須有一個心跳機制來保持每一個節點是可達的,監控的,而且節點的信息變動,也能夠經過心跳中的數據包來傳遞。這樣就很容易理解Redis 的心跳機制是怎麼實現的。這有點相似於主從複製中的實現方法,總之就是一個心跳。在 redis cluster 只,這種心跳又叫 gossip 機制。

集羣之間交互信息是用其內部專用鏈接的。redis cluster 中的每個節點都監聽了一個集羣專用的端口,專門用做集羣節點之間的信息交換。在 redis cluster 初始化函數 clusterInit() 中監聽了該端口,並在事件中心註冊了 clusterAcceptHandler()。 從 clusterAcceptHandler()的邏輯來看,當有新的鏈接到來時,會爲新的鏈接註冊 clusterReadHandler()回調函數。這一點和 redis 自己初始化的行爲是一致的。

clusterSendPing() 中發送心跳數據。發送的包括:所知節點的名稱,ip 地址等。這樣,改節點就將主機所知的信息傳播到了其餘的節點。注意,從 clusterSendPing() 的實現來看,redis cluster 並非一開始就向全部的節點發送心跳數據,而選取幾個節點發送,由於 redis 考慮到集羣網的造成並不須要每一個節點向像集羣中的全部其餘節點發送 ping。

故障修復

故障修復曾經在主從複製中提到過。redis cluster 的故障修復分兩種途徑,一種是集羣自治實現的故障修復,一種是人工觸發的故障修復。

集羣自治實現的故障修復中,是由從機發起的。上面所說,集羣中的每一個節點都須要和其餘節點保持鏈接。從機若是檢測到主機節點出錯了(標記爲 REDIS_NODE_FAIL),會嘗試進行主從切換。在 cluster 定時處理函數中,有一段只有從機纔會執行的代碼段:

// 集羣定時處理函數
/* This is executed 10 times every second */
void clusterCron(void) {
    ......
    // 從機才須要執行下面的邏輯
    if (nodeIsSlave(myself)) {
        ......
        // 從機-> 主機替換
        clusterHandleSlaveFailover();
        ......
    }
    ......
}

從機的 clusterCron() 會調用 clusterHandleSlaveFailover() 已決定是否須要執行故障修復。一般,故障修復的觸發點就是在其主機被標記爲出錯節點的時候。

故障修復的協議

在決定故障修復後,會開始進行協商是否能夠將本身升級爲主機。

// 主機已是一個出錯節點了,本身做爲從機能夠升級爲主機
void clusterHandleSlaveFailover(void) {
    ......
    // 故障修復超時,從新啓動故障修復
    if (auth_age > auth_retry_time) { // 兩次故障修復間隔不能太短
        // 更新一些時間
        ......
        redisLog(REDIS_WARNING,
        "Start of election delayed for %lld milliseconds "
        "(rank #%d, offset %lld).",
        server.cluster->failover_auth_time - mstime(),
        server.cluster->failover_auth_rank,
        replicationGetSlaveOffset());
        // 告知其餘從機
        /* Now that we have a scheduled election, broadcast our offset
        * to all the other slaves so that they'll updated their offsets
        * if our offset is better. */
        clusterBroadcastPong(CLUSTER_BROADCAST_LOCAL_SLAVES);
        return;
    }
    ......
    // 開頭投票
    /* Ask for votes if needed. */
    if (server.cluster->failover_auth_sent == 0) {
        server.cluster->currentEpoch++;
        server.cluster->failover_auth_epoch = server.cluster->currentEpoch;
        redisLog(REDIS_WARNING,"Starting a failover election for epoch %llu.",
        (unsigned long long) server.cluster->currentEpoch);
        clusterRequestFailoverAuth();
        server.cluster->failover_auth_sent = 1;
        clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
        CLUSTER_TODO_UPDATE_STATE|
        CLUSTER_TODO_FSYNC_CONFIG);
        return; /* Wait for replies. */
    }
}

上面有兩個關鍵的函數:clusterBroadcastPong() 和clusterRequestFailoverAuth()。

在 clusterBroadcastPong()中,會向其餘屬於同一主從關係的其餘從機發送 pong,以傳遞主機已經出錯的信息。

clusterRequestFailoverAuth() 中,會向集羣中的全部其餘節點發送 CLUSTERMSG_-TYPE_FAILOVER_AUTH_REQUEST 信令,意即詢問是否投票。

那收到這個信令的節點,是否會向源節點投票呢?先來看看 FAILOVERAUTH-REQUEST 信令中帶有什麼數據,順着 clusterRequestFailoverAuth() 下去,會找到 cluster-BuildMessageHdr() 函數,它打包了一些數據。這裏主要包括:

  1. RCmb 四個字符,至關因而 redis cluster 信令的頭部校驗值,
  2. type,命令號,這是屬於什麼信令
  3. sender info,發送信令節點的信息
  4. 發送信令節點的配置版本號
  5. 主從複製偏移量

在心跳機制那一節講過,集羣節點會爲與其餘節點的鏈接註冊clusterReadHandler() 回調函數,FAILOVER_AUTH_REQUEST 信令的處理也在裏面,對應的是 clusterSendFailover-AuthIfNeeded() 處理函數,在這裏決定是否投對端節點一票。這裏的決定因素有幾個:配置版本號,節點自己和投票時間。

1.若是須要投票,索取投票的節點當前版本號必須比當前記錄的版本同樣,這樣纔有權索取投票;新的版本號必須是最新的。第二點,可能比較繞,譬以下面的場景,slave 是沒法得到其餘主機的投票的,other slave 才能夠。這裏的意思是,若是一個從機想要升級爲主機,它與它的主機必須保持狀態一致。

2.索取投票的節點必須是從機節點。這是固然,由於故障修復是由從機發起的

3.最後一個是投票的時間,由於當一個主機有多個從機的時候,多個從機都會發起故障修復,一段時間內只有一個從機會進行故障修復,其餘的會被推遲。

這三點都在 clusterSendFailoverAuthIfNeeded() 中都有所體現。

當都知足了上述要求事後,便可開始投票:

// 決定是否投票,redis cluster 將根據配置的版本號決定是否投票
/* Vote for the node asking for our vote if there are the conditions. */
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) {
    ......
    // 投票走起
    /* We can vote for this slave. */
    clusterSendFailoverAuth(node);
    server.cluster->lastVoteEpoch = server.cluster->currentEpoch;
    node->slaveof->voted_time = mstime(); // 更新投票的時間
    redisLog(REDIS_WARNING, "Failover auth granted to %.40s for epoch %llu",
    node->name, (unsigned long long) server.cluster->currentEpoch);
}

投票函數 lusterSendFailoverAuth() 只是放一個 CLUSTERMSG_TYPEFAILOVER-AUTH_ACK 信令到達索取投票的從機節點,從而該從機獲取了一票。讓咱們再回到索取投票的從機節點接下來會怎麼作。

// 主機已是一個出錯節點了,本身做爲從機能夠升級爲主機
void clusterHandleSlaveFailover(void) {
    ......
    // 得到的選票必須是集羣節點數的通常以上
    /* Check if we reached the quorum. */
    if (server.cluster->failover_auth_count >= needed_quorum) {
        /* We have the quorum, we can finally failover the master. */
        redisLog(REDIS_WARNING,
        "Failover election won: I'm the new master.");
        /* Update my configEpoch to the epoch of the election. */
    if (myself->configEpoch < server.cluster->failover_auth_epoch) {
        myself->configEpoch = server.cluster->failover_auth_epoch;
        redisLog(REDIS_WARNING,
        "configEpoch set to %llu after successful failover",
        (unsigned long long) myself->configEpoch);
    }
    // 正式轉換爲主機,代替主機的功能
    /* Take responsability for the cluster slots. */
    clusterFailoverReplaceYourMaster();
    } else {
        clusterLogCantFailover(REDIS_CLUSTER_CANT_FAILOVER_WAITING_VOTES);
    }
}

從機在獲取集羣節點數量半數以上的投票時,就能夠正式升級爲主機了。來回顧一下投票的全過程:

clusterFailoverReplaceYourMaster() 就是將其自身的配置從從機更新到主機,最後廣播給全部的節點:我轉正了。實際上,是發送一個 pong。

redis cluster 還提供了一種人工故障修復的模式,管理人員能夠按需使用這些功能。你能夠從 clusterCommand() 下找到人工故障修復流程開始執行的地方:

1.cluster failover takeover. 會強制將從機升級爲主機,不須要一個投票的過程。

2.cluster failover force. 會強制啓用故障修復,這和上面講的故障修復過程同樣。若是你留意 clusterHandleSlaveFailover() 中的處理邏輯的話,實際 cluster force 也是在其中處理的,一樣須要一個投票的過程。

3.cluster failover. 默認的模式,會先告知主機須要開始進行故障修復流程,主機被告知會中止服務。以後再走接下來的主從修復的流程。

// cluster 命令處理。
void clusterCommand(redisClient *c) {
    ......
    // 啓動故障修復
    } else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
        (c->argc == 2 || c->argc == 3))
    {
    ......
    if (takeover) {
        ......
        // 產生一個新的配置版本號
        clusterBumpConfigEpochWithoutConsensus();
        // 直接將本身升級爲主機,接着通知到全部的節點
        clusterFailoverReplaceYourMaster();
    } else if (force) {
        ......
        // 直接標記爲能夠開始進行故障修復了,並不用告知主機
        server.cluster->mf_can_start = 1;
    } else {
        // 先通知個人主機開始人工故障修復,再執行接下來的故障修復流程
        clusterSendMFStart(myself->slaveof);
        }
        addReply(c,shared.ok);
    }
    ......
}

4.發送信令節點的配置版本號

5.主從複製偏移量

人工故障修復模式,和自治實現的故障修復模式最大的區別在於對於從機來講,其主機是否可達。人工故障修復模式,容許主機可達的狀況下,實現故障修復。所以,相比自治的故障修復,人工的還會多一道工序:主從複製的偏移量相等事後,纔開始進行故障修復的過程。

從下面兩種模式的處理來看,有很明顯的區別:

// cluster 命令處理
void clusterCommand(redisClient *c) {
    ......
    // 啓動故障修復
    } else if (!strcasecmp(c->argv[1]->ptr,"failover") &&
        (c->argc == 2 || c->argc == 3))
    {
        ......
    if (takeover) {
        ......
        // 產生一個新的配置版本號
        clusterBumpConfigEpochWithoutConsensus();
        // 直接將本身升級爲主機,接着通知到全部的節點
        clusterFailoverReplaceYourMaster();
    } else if (force) {
        ......
        // 直接標記爲能夠開始進行故障修復了,並不用告知主機
        server.cluster->mf_can_start = 1;
    } else {
        // 先通知個人主機開始人工故障修復,再執行接下來的故障修復流程
        clusterSendMFStart(myself->slaveof);
        }
    addReply(c,shared.ok);
    }
}
  1. takeover 模式直接將本身升級爲主機
  2. force 模式直接進入故障修復模式
  3. 默認模式會先告知(clusterSendMFStart())主機,接着再進行故障修復流程

來看看人工故障修復模式的狀態機 clusterHandleManualFailover(),這個函數只會在 clusterCron() 中調用:

// 人工恢復狀態機, 只在clusterCron() 中調用
void clusterHandleManualFailover(void) {
    /* Return ASAP if no manual failover is in progress. */
    if (server.cluster->mf_end == 0) return;
        /* If mf_can_start is non-zero, the failover was already triggered so the
        * next steps are performed by clusterHandleSlaveFailover(). */
    if (server.cluster->mf_can_start) return;
    if (server.cluster->mf_master_offset == 0) return; /* Wait for offset... */
    if (server.cluster->mf_master_offset == replicationGetSlaveOffset()) {
        /* Our replication offset matches the master replication offset
        * announced after clients were paused. We can start the failover. */
        server.cluster->mf_can_start = 1;
        redisLog(REDIS_WARNING,
        "All master replication stream processed, "
        "manual failover can start.");
    }
}

主要的幾個變量這裏解說一下:

  1. mf_end:在觸發人工故障修復的時候就會被設置
  2. mf_master_offset:從機須要等待主機發送主從複製偏移量,如上所說,從機升級爲 主機,須要和主機的偏移量相等
  3. mf_can_start:主從機偏移量相等時,就能夠進行故障修復了

自治故障修復和人工故障修復流程都是在 clusterHandleSlaveFailover() 中開始執行的。這裏再也不復述。

這裏大概總結一下人工故障修復默認模式的流程:

數據遷移

在以前有講過 migrate 系列的命令,即數據遷移。在 redis cluster 中,搬遷 slot 的時候,就會用到 migrate 系列的命令。

爲了管理鏈接,redis cluster 還實現了長鏈接的管理,你能夠在 migrateGetSocket() 中查看它的實現。

在集羣狀態結構體中存儲了兩個與數據遷移的數據:

typedef struct clusterState {
    ......
    // slot 數據正在遷移到migrating_slots_to[slot] 節點
    clusterNode *migrating_slots_to[REDIS_CLUSTER_SLOTS];
    // slot 數據正在從importing_slots_from[slot] 遷移到本機
    clusterNode *importing_slots_from[REDIS_CLUSTER_SLOTS];
    ......
} clusterState;

這些信息在數據訪問的時候會有用。

相關文章
相關標籤/搜索