Redis 複製機制詳解

Redis 複製技術是實現 Redis 哨兵、集羣高可用部署和 Redis 高擴展性的基石。Redis 從實例通過複製主實例使得主從之間數據達到最終一致性,複製過程包括複製初始化、數據同步和命令傳播三個階段。

複製初始化

Redis 實例收到 replicaof {MASTER_IP} {MASTER_PORT} 命令(老版本中使用 slaveof 命令)後與主實例進行連接並與主實例同步數據。執行 replicaof 命令後,Redis 實例會進行如下操作:

如果該節點之前有連接的其他主實例,則該命令會執行切主操作:

數據同步

當從實例首次連接主實例、執行了切主操作或者網絡出現閃斷後,從實例會與主實例進行數據同步。Redis 中數據同步包括全量同步和部分同步兩種,涉及到 SYNC 和 PSYNC 兩個命令。

SYNC 命令

在 Redis2.8 之前,Redis 只支持 SYNC 命令。當收到從實例發送的 SYNC 命令時,主實例與從實例進行全量同步,其操作流程如下:

PSYNC 命令

在 Redis2.8 之前,主從之間即使短暫的網絡閃斷也會觸發全量複製,全量複製對於 Redis 來說是一種高 IO、高內存佔用、高用時的操作。爲了解決該問題,在 Redis2.8 版本中引入了部分複製機制和其相應的命令 PSYNC,其命令的格式爲 psync {RUN_ID} {OFFSET}。Redis 使用複製偏移量、複製積壓緩衝區和運行 id(psync2 中改爲複製 id)三部分來支持該命令。

複製偏移量

主實例和從實例雙方都會分別維護一個複製偏移量,用以記載當前實例發送 / 接受到的命令的長度

從實例每秒鐘使用 replconf ack {OFFSET} 命令向主實例上報自己的複製偏移量,通過對比主從實例的複製偏移量,可以判斷主從實例是否處於一致的狀態。

複製積壓緩衝區

複製積壓緩衝區是主實例維護的一個固定長度(參數 repl-backlog-size 設置,默認爲 1M)的先進先出隊列,當主實例進行命令傳播時,不僅會將寫命令同步給從實例,同時也會把寫命令寫入複製積壓緩衝區。當從實例重新連接主實例時,主實例通過比較自身和從實例的複製偏移量,判斷從實例缺少的數據是否還在複製積壓緩衝區中,以此來決定對從實例進行全量同步還是部分同步。

主實例運行 ID

主實例運行 ID 是一個長度爲 40 位的 16 進制字符串,用於唯一標識一個 Redis 節點,每次在節點啓動均會重新生成。從實例保存主實例的運行 id 標識自己複製的是哪個主實例,主實例判斷 psync 命令中的 run_id 是否與自身的相同,若不相同則進行全量複製。

PSYNC2

Redis 在 4.0 版本之前使用 psync 命令解決了主從短暫掉線後必須進行全量複製的問題。但是在以下兩個常見的運維場景仍會發生全量複製:

  1. 從實例重啓後丟失了以前 run_id 和複製偏移量

  2. 發生了主從切換,主實例的 run_id 發生了變化

Redis 4.0 對 PSYNC 命令進行了升級,解決在上述兩種場景下必然引起全量複製的問題。PYSNC2 的命令格式爲 PSYNC {REPL_ID} {OFFSET},其中 REPL_ID 複製 ID,用以替代原先 PSYNC1 命令中的主實例 run_id。

針對從實例重啓的場景

在 Redis 關閉保存 RDB 文件時,會將當前實例的 repl_id 和 offset 保存到 RDB 文件中,並在啓動後從 RDB 文件加載回來。

int rdbSaveInfoAuxFields(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
    ...
    
        if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid)
            == -1) return -1;
        if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset)
            == -1) return -1;
    ...
    return 1;
}
int rdbLoadRio(rio *rdb, int rdbflags, rdbSaveInfo *rsi) {
    ...
        else if (type == RDB_OPCODE_AUX) {
            ...
            else if (!strcasecmp(auxkey->ptr,"repl-id")) {
                if (rsi && sdslen(auxval->ptr) == CONFIG_RUN_ID_SIZE) {
                    memcpy(rsi->repl_id,auxval->ptr,CONFIG_RUN_ID_SIZE+1);
                    rsi->repl_id_is_set = 1;
                }
            } else if (!strcasecmp(auxkey->ptr,"repl-offset")) {
                if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);
            }
            ...
       }
    ...
}
針對主從切換的場景

每個 Redis 實例均會保存兩個 repli_id 及它們的複製偏移量,可使用 info replication 命令查看:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=172.16.0.205,port=6379,state=online,offset=392936296,lag=0
master_replid:2ac88837548c56c416f41fd0daf994d97a924dbe
master_replid2:7bf82d2358bcf03c22c1447fc7ee5f75527f8673
master_repl_offset:392936440
second_repl_offset:5156450
...

master_replid 和 master_repl_offset 是當前 master 的複製 id 和複製偏移量,master_replid2 和 second_repl_offset 是本實例上一個 master 的複製 id 和對應的複製偏移量。在發生主從切換的時候,從實例發送給主實例的的 repli_id 會與主實例當前保存的 master_replid2 字段值相同,繼而可以進行部分同步。

int masterTryPartialResynchronization(client *c) {
    char *master_replid = c->argv[1]->ptr;
    ...
    if (strcasecmp(master_replid, server.replid) &&
        (strcasecmp(master_replid, server.replid2) ||
         psync_offset > server.second_replid_offset))
    {
        ...
        goto need_full_resync;
    }

    /* We still have the data our slave is asking for? */
    if (!server.repl_backlog ||
        psync_offset < server.repl_backlog_off ||
        psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
    {
        ...
        goto need_full_resync;
    }

    ...
    if (c->slave_capa & SLAVE_CAPA_PSYNC2) {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE %s\r\n", server.replid);
    } else {
        buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
    }
    if (connWrite(c->conn,buf,buflen) != buflen) {
        freeClientAsync(c);
        return C_OK;
    }
    psync_len = addReplyReplicationBacklog(c,psync_offset);
    ...
}
PSYNC2 命令流程

psync2 執行流程

命令傳播

在主實例接受到命令後,首先判斷是否對數據庫做了改變,如果數據已經改變則將命令寫入自身複製緩衝區並將命令傳播給從實例,同時增加自身複製偏移量。

結語

Redis 複製技術爲 Redis 哨兵模式、集羣模式的部署提供了支撐。從全量複製到部分複製,從 PSYNC 到 PSYNC2,Redis 不斷對複製機制進行優化,但需要注意的是 Redis 保證的是最終一致性,故在某些極端的場景下從原理上做不到數據不丟失。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/GwCB9ng5nnBx6BPHLLsWew