Redis4.0新特性(二)-PSYNC2

Redis4.0新特性psync2(partial resynchronization version2)部分重新同步(partial resync)增加版本;
主要解決Redis運維管理過程中,從實例重啟和主實例故障切換等場景帶來的全量重新同步(full resync)問題。

什么是Redis部分重新同步psync

redis部分重新同步:是指redis因某種原因引起復制中斷后,從庫重新同步時,只同步主實例的差異數據(寫入指令),不進行bgsave復制整個RDB文件。

本文的名詞規約:
部分重新同步:后文簡稱psync
全量重新同步:后文簡稱fullsync
redis2.8第一版部分重新同步:后文簡稱psync1
redis4.0第二版本部分重新同步:后文簡稱psync2

在說明psync2功能前,先簡單闡述redis2.8版本發布的psync1

Redis2.8 psync1解決什么問題

在psync1功能出現前,redis復制秒級中斷,就會觸發從實例進行fullsync。
每一次的fullsync,集群的性能和資源使用都可能帶來抖動;如果redis所處的網絡環境不穩定,那么fullsync的出步頻率可能較高。
為解決此問題,redis2.8引入psync1, 有效地解決這種復制閃斷,帶來的影響。

redis的fullsync對業務而言,算是比較“重”的影響;對性能和可用性都有一定危險。
這里列舉幾個fullsync常見的影響:

    1. master需運行bgsave,出現Fork,可能造成master達到毫秒或秒級的卡頓(latest_fork_usec);
    2. redis進程Fork導致Copy-On-Write內存使用消耗(后文簡稱COW),最大能導致master進程內存使用量的消耗。    
    (eg RDB: 5213 MB of memory used by copy-on-write)
    3. Redis Slave load RDB過程,會導致復制線程的client output buffer增長很大;增大Master進程內存消耗;
    4. Redis保存RDB(不考慮disless replication),導致服務器磁盤IO和CPU(壓縮)資源消耗
    5. 發送數GB大小的RDB文件,會導致服務器網絡出口爆增,如果千兆網卡服務器,期間會影響業務正常請求響應時間(以及其他連鎖影響)

psync1的基本實現

redis2.8為支持psync1,引入了replication backlog buffer(后文稱:復制積壓緩沖區);

復制積壓緩沖區是redis維護的固定長度緩沖隊列(由參數repl-backlog-size設置,默認1MB),
master的寫入命令在同步給slaves的同時,會在緩沖區中寫入一份(master只有1個積壓緩沖區,所有slaves共享)。

當redis復制中斷后,slave會嘗試采用psync, 上報原master runid + 當前已同步master的offset(復制偏移量,類似mysql的binlog file和position);
如果runid與master的一致,且復制偏移量在master的復制積壓緩沖區中還有(即offset >= min(backlog值),master就認為部分重同步成功,不再進行全量同步。

部分重同步成功,master的日志顯示如下:

30422:M 04 Aug 14:33:48.505 * Slave xxxxx:10005 asks for synchronization
30422:M 04 Aug 14:33:48.506 * Partial resynchronization request from xxx:10005 accepted. Sending 0 bytes of backlog starting from offset 6448313.

redis2.8的部分同步機制,有效解決了網絡環境不穩定、redis執行高時間復雜度的命令引起的復制中斷,從而導致全量同步。但在應對slave重啟和Master故障切換的場景時,psync1還是需進行全量同步。

psync1的不足

從上文可知,psync1需2個條件同時滿足,才能成功psync: master runid不變 和復制偏移量在master復制積緩沖區中。
那么在redis slave重啟,因master runid和復制偏移量都會丟失,需進行全量重同步;
redis master發生故障切換,因master runid發生了變化;故障切換后,新的slave需進行全量重同步。

而slave維護性重啟、master故障切換都是redis運維常見場景,為redis的psync1是不能解決這兩類場景的成功部分重同步問題。

因此redis4.0的加強版部分重同步功能-psync2,主要解決這兩類場景的部分重新同步。

psync2的實現簡述

在redis cluster的實際生產運營中,實例的維護性重啟、主實例的故障切換(如cluster failover)操作都是比較常見的(如實例升級、rename command和釋放實例內存碎片等)。而在redis4.0版本前,這類維護性的處理,redis都會發生全量重新同步,導到性能敏感的服務有少量受損。
如前文所述,psync2主要讓redis在從實例重啟和主實例故障切換場景下,也能使用部分重新同步。
本節主要簡述psync2在這兩種場景的邏輯實現。
名詞解釋:

  • master_replid : 復制ID1(后文簡稱:replid1),一個長度為41個字節(40個隨機串+'\0')的字符串。redis實例都有,和runid沒有直接關聯,但和runid生成規則相同,都是由getRandomHexChars函數生成。當實例變為從實例后,自己的replid1會被主實例的replid1覆蓋。
  • master_replid2:復制ID2(后文簡稱:replid2),默認初始化為全0,用于存儲上次主實例的replid1

實例的replid信息,可通過info replication進行查看; 示例如下:

127.0.0.1:6385> info replication
# Replication
role:slave
master_host:xxxx      // IP模糊處理
master_port:6382
master_link_status:up
slave_repl_offset:119750
master_replid:fe093add4ab71544ce6508d2e0bf1dd0b7d1c5b2  //這里是主實例的replid1相同
master_replid2:0000000000000000000000000000000000000000  //未發生切換,即主實例未發生過變化,所以是初始值全"0"
master_repl_offset:119750
second_repl_offset:-1

Redis從實例重啟的部分重新同步

在之前的版本,redis重啟后,復制信息是完全丟失;所以從實例重啟后,只能進行全量重新同步。
redis4.0為實現重啟后,仍可進行部分重新同步,主要做以下3點:
1 redis關閉時,把復制信息作為輔助字段(AUX Fields)存儲在RDB文件中;以實現同步信息持久化。
2 redis啟動加載RDB文件時,會把復制信息賦給相關字段;為部分同步
3 redis重新同步時,會上報repl-id和repl-offset同步信息,如果和主實例匹配,且offset還在主實例的復制積壓緩沖區內,則只進行部分重新同步。

接下來,我們詳細分析每步的簡單實現

redis關閉時,持久化復制信息到RDB

redis在關閉時,通過shutdown save,都會調用rdbSaveInfoAuxFields函數,
把當前實例的repl-id和repl-offset保存到RDB文件中。
說明:當前的RDB存儲的數據內容和復制信息是一致性的。熟悉MySQL的同學,可以認為MySQL中全量備份數和binlog信息是一致的。
rdbSaveInfoAuxFields函數實現在rdb.c源文件中,省略后代碼如下:

/* Save a few default AUX fields with information about the RDB generated. */
int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {

    /* Add a few fields about the state when the RDB was created. */
    if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;
    
    //把實例的repl-id和repl-offset作為輔助字段,存儲在RDB中
    if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid) == -1) return -1;
    if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset) == -1) return -1;
    return 1;
}

生成的RDB文件,可以通過redis自帶的redis-check-rdb工具查看輔助字段信息。
其中repl兩字段信息和info中的相同;

$shell> /src/redis-check-rdb  dump.rdb      
[offset 0] Checking RDB file dump.rdb
[offset 26] AUX FIELD redis-ver = '4.0.1'
[offset 133] AUX FIELD repl-id = '44873f839ae3a57572920cdaf70399672b842691'
[offset 148] AUX FIELD repl-offset = '0'
[offset 167] \o/ RDB looks OK! \o/
[info] 1 keys read
[info] 0 expires
[info] 0 already expired
redis啟動讀取RDB中復制信息

redis實例啟動讀取RDB文件,通過rdb.c文件中rdbLoadRio()函數實現。
redis加載RDB文件,會專門處理文件中輔助字段(AUX fields)信息,把其中repl_id和repl_offset加載到實例中,分別賦給master_replid和master_repl_offset兩個變量值。
以下代碼,是從RDB文件中讀取兩個輔助字段值。

int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
----------省略-----------

else if (!strcasecmp(auxkey->ptr,"repl-id")) {//讀取的aux字段是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);
            } else {
                /* We ignore fields we don't understand, as by AUX field
                 * contract. */
                serverLog(LL_DEBUG,"Unrecognized RDB AUX field: '%s'",
                    (char*)auxkey->ptr);
            }
}
redis從實例嘗試部分重新同步

redis實例重啟后,從RDB文件中加載(注:此處不討論AOF和RDB加載優先權)master_replid和master_repl_offset;相當于實例的server.cached_master。當我們把它作為某個實例的從庫時(包含如被動的cluster slave或主動執行slaveof指令),實例向主實例上報master_replid和master_repl_offset+1;從實例同時滿足以下兩條件,就可以部分重新同步:
1 從實例上報master_replid串,與主實例的master_replid1或replid2有一個相等
2 從實例上報的master_repl_offset+1字節,還存在于主實例的復制積壓緩沖區中

從實例嘗試部分重新同步函數slaveTryPartialResynchronization(replication.c文件中);
主實例判斷能否進行部分重新同步函數masterTryPartialResynchronization(replication.c文件中)。

redis重啟時,臨時調整主實例的復制積壓緩沖區大小

redis的復制積壓緩沖區是通過參數repl-backlog-size設置,默認1MB;為確保從實例重啟后,還能部分重新同步,需設置合理的repl-backlog-size值。
1 計算合理的repl-backlog-size值大小
通過主庫每秒增量的master復制偏移量master_repl_offset(info replication指令獲取)大小,
如每秒offset增加是5MB,那么主實例復制積壓緩沖區要保留最近60秒寫入內容,backlog_size設置就得大于300MB(60*5)。而從實例重啟加載RDB文件是較耗時的過程,如重啟某個重實例需120秒(RDB大小和CPU配置相關),那么主實例backlog_size就得設置至少600MB.

計算公式:backlog_size = 重啟從實例時長 * 主實例offset每秒寫入量

2 重啟從實例前,調整主實例的動態調整repl-backlog-size的值。
通過config set動態調整redis的repl-backlog-size時,redis會釋放當前的積壓緩沖區,重新分配一個指定大小的緩沖區。 所以我們必須在從實例重啟前,調整主實例的repl-backlog-size。
調整backlog_size處理函數resizeReplicationBacklog,代碼邏輯如下:

void resizeReplicationBacklog(long long newsize) {
    if (newsize < CONFIG_REPL_BACKLOG_MIN_SIZE) //?如果設置新值小于16KB,則修改為16KB
        newsize = CONFIG_REPL_BACKLOG_MIN_SIZE;
    if (server.repl_backlog_size == newsize) return; //如果新值與原值相同,則不作任何處理,直接返回。

    server.repl_backlog_size = newsize;  //修改backlog參數大小
    if (server.repl_backlog != NULL) { //當backlog內容非空時,釋放當前backlog,并按新值分配一個新的backlog
        /* What we actually do is to flush the old buffer and realloc a new
         * empty one. It will refill with new data incrementally.
         * The reason is that copying a few gigabytes adds latency and even
         * worse often we need to alloc additional space before freeing the
         * old buffer. */
        zfree(server.repl_backlog);
        server.repl_backlog = zmalloc(server.repl_backlog_size);
        server.repl_backlog_histlen = 0;  //修改backlog內容長度和首字節offset都為0
        server.repl_backlog_idx = 0;
        /* Next byte we have is... the next since the buffer is empty. */
        server.repl_backlog_off = server.master_repl_offset+1;
    }
}

psync2實現Redis Cluster Failover部分全新同步

為解決主實例故障切換后,重新同步新主實例數據時使用psync,而分fullsync;

1 redis4.0使用兩組replid、offset替換原來的master runid和offset.
2 redis slave默認開啟復制積壓緩沖區功能;以便slave故障切換變化master后,其他落后從可以從緩沖區中獲取寫入指令。
第一組:master_replid和master_repl_offset
如果redis是主實例,則表示為自己的replid和復制偏移量; 如果redis是從實例,則表示為自己主實例的replid1和同步主實例的復制偏移量。

第二組:master_replid2和second_repl_offset
無論主從,都表示自己上次主實例repid1和復制偏移量;用于兄弟實例或級聯復制,主庫故障切換psync.
初始化時, 前者是40個字符長度為0,后者是-1; 只有當主實例發生故障切換時,redis把自己replid1和master_repl_offset+1分別賦值給master_replid2和second_repl_offset。
這個交換邏輯實現在函數shiftReplicationId中。

void shiftReplicationId(void) {
    memcpy(server.replid2,server.replid,sizeof(server.replid)); //replid賦值給replid2
    /* We set the second replid offset to the master offset + 1, since
     * the slave will ask for the first byte it has not yet received, so
     * we need to add one to the offset: for example if, as a slave, we are
     * sure we have the same history as the master for 50 bytes, after we
     * are turned into a master, we can accept a PSYNC request with offset
     * 51, since the slave asking has the same history up to the 50th
     * byte, and is asking for the new bytes starting at offset 51. */
    server.second_replid_offset = server.master_repl_offset+1; 
    changeReplicationId();
    serverLog(LL_WARNING,"Setting secondary replication ID to %s, valid up to offset: %lld. New replication ID is %s", server.replid2, server.second_replid_offset, server.replid);
}

這樣發生主庫故障切換,以下三種常見結構,都能進行psync:
1 一主一從發生切換,A->B 切換變成 B->A ;
2 一主多從發生切換,兄弟節點變成父子節點時;
3 級別復制發生切換, A->B->C 切換變成 B->C->A

主實例判斷能否進行psync的邏輯函數在masterTryPartialResynchronization()

int masterTryPartialResynchronization(client *c) {
 
    //如果slave提供的master_replid與master的replid不同,且與master的replid2不同,或同步速度快于master; 就必須進行fullsync.
    if (strcasecmp(master_replid, server.replid) &&
        (strcasecmp(master_replid, server.replid2) ||
         psync_offset > server.second_replid_offset))
    {
        /* Run id "?" is used by slaves that want to force a full resync. */
        if (master_replid[0] != '?') {
            if (strcasecmp(master_replid, server.replid) &&
                strcasecmp(master_replid, server.replid2))
            {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Replication ID mismatch (Slave asked for '%s', my "
                    "replication IDs are '%s' and '%s')",
                    master_replid, server.replid, server.replid2);
            } else {
                serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
                    "Requested offset for second ID was %lld, but I can reply "
                    "up to %lld", psync_offset, server.second_replid_offset);
            }
        } else {
            serverLog(LL_NOTICE,"Full resync requested by slave %s",
                replicationGetSlaveName(c));
        }
        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))
    {
        serverLog(LL_NOTICE,
            "Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).", replicationGetSlaveName(c), psync_offset);
        if (psync_offset > server.master_repl_offset) {
            serverLog(LL_WARNING,
                "Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
        }
        goto need_full_resync;
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 摘要:1 什么是Redis部分重新同步-psync redis部分重新同步:是指redis因某種原因引起復制中斷后...
    暖夏未眠丶閱讀 1,063評論 0 3
  • 淺談Redis主從復制 2013.09.27 11:27:00 來源: 京東 作者:張成遠 ( 0 條評論 )...
    epime閱讀 575評論 0 2
  • 1.1 資料 ,最好的入門小冊子,可以先于一切文檔之前看,免費。 作者Antirez的博客,Antirez維護的R...
    JefferyLcm閱讀 17,087評論 1 51
  • Redis 配置文件示例 注意:想要讀取配置文件,Redis的第一個參數必須是文件的路徑 ./redis-serv...
    起個名忒難閱讀 1,217評論 0 1
  • 今天周日。爸爸一早就能量滿滿,溫柔的叫醒孩子,孩子有些起床氣,對爸爸和她討論的今日安排不感冒,爸爸及時停止去給孩子...
    爬山虎7544閱讀 360評論 3 2