redis 備份過程代碼分析

在redis中, RDB主要用來進行數據庫的全量備份, AOF主要用來進行增量備份, 他們有不同的使用場景, 在真實的線上環境, 比較常見的是結合全量備份和增量備份來實現按時間點的回覆。

RDB備份

RDB是對備份時間點的數據庫現有數據的一個snapshot, 它的基本原理是遍歷數據庫內指定的或者全部庫, 將數據按照比較緊湊的方式保存在一份數據裏面。這裏, 比較緊湊的意思是將數據以特定的格式保存, 並且還可以以LZF壓縮的方式存儲(需要配置)。
RDB 的備份分爲2種方式:自動備份和手動備份。
自動備份是在serverCron裏面實現, 每當配置文件中指定的自動備份條件滿足的時候, 系統自動進行備份。比如, 在配置裏面指定如下的備份條件:

save 900 1
save 300 10
save 60 10000

相關的處理代碼如下:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            // RDB 或者AOF 子進程返回值的處理
    } else {
        // 當如下的條件之一滿足後, 調用RDB自動備份
        for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

同時, 也看到自動備份的入口爲rdbSaveBackground。
手工備份, 其入口函數是:

struct redisCommand redisCommandTable[] = {
    {"save",saveCommand,1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},

    {"bgsave",bgsaveCommand,-1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},
}

手工備份,有前臺備份和後臺備份。
前臺備份通過rdbSave來實現, 它是由主進程操作實現, 也就是說,在主進程實現RDB前臺備份的過程中, 是無法同時接受客戶端的相應的, 顯然這種方式的缺點很明顯:無法應對高併發的場景, 優點就是處理簡單;
後臺備份是通過rdbSaveBackground來實現, 通過主進程啓動一個新的進程, 該子進程調用rdbSave產生備份文件,主子進程之間通過pipe的方式啓動一個無名管道,進行信息的傳遞, 比如,子進程COW數據的大小。 這種備份的好處是不會影響對外提供服務, 因爲整個備份過程是異步操作, 缺點就是處理起來相對複雜一些。

AOF備份

AOF備份數據

AOF是將影響到redis數據庫狀態的所有操作記錄下來並寫入到一個指定的AOF文件裏面, 當需要的時候, 通過回放AOF文件內的一條條redis命令, 來重新構建內存狀態, 達到回覆數據庫的目的。它非常類似於數據庫的操作日誌的同步與回放。
redis裏面的命令非常多, 他們使用一樣的接口來處理, 那些命令需要記錄在AOF裏, 哪些不需要,是通過在調用call()之前是否是定PROPAGATE_AOF flag來表明該操作是否需要記錄到AOF, master-slave之間是否需要同步命令, 使用一樣的操作方式。

void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
               int flags)
{
    if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
        feedAppendOnlyFile(cmd,dbid,argv,argc);
    if (flags & PROPAGATE_REPL)
        replicationFeedSlaves(server.slaves,dbid,argv,argc);
}

feedAppendOnlyFile裏面記錄了需要記錄在AOF中各種命令的存儲格式, 也就是AOF協議格式,以便讀取的時候能夠得到正確的回放。
每一條命令是如果都要寫入AOF文件然後返回客戶端, 就會在成太頻繁的IO操作, 影響redis的系統性能; 不加以控制由系統決定又無法保證數據的落盤, 因此, redis通過如下的配置項來指定:

appendfsync always/everysec/ no

通常, 我們需要續訂everysec, 兼顧性能和數據安全性。

AOF隨着時間的推移會越變越大, 回放的時間也就會越來越長, 爲了提升回放的效率, redis會定期重寫AOF。

AOF重寫

AOF重寫操作的觸發有2中方式:用戶觸發與後臺週期檢測觸發。
用戶觸發是由命令bgrewriteaof進行, 對應的入口函數爲

bgrewriteaofCommand:
struct redisCommand redisCommandTable[] = {
    {"bgrewriteaof",bgrewriteaofCommand,1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},
}

後臺週期性的檢測觸發, 它的觸發點也在serverCron裏面, 當沒有RDB或者AOF 重寫操作的時候, 系統會自動檢查AOF的大小以及相對於上次重寫時的AOF大小, 決定是否進行重寫操作。如下2個配置項可以指定觸發的條件:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

代碼的實現如下:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
        ldbPendingChildren())
    {
        int statloc;
        pid_t pid;

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            // RDB 或者AOF 子進程返回值的處理
    } else {
        /* If there is not a background saving/rewrite in progress check if
         * we have to save/rewrite now. */

        /* Trigger an AOF rewrite if needed. */
        if (server.aof_state == AOF_ON &&
            server.rdb_child_pid == -1 &&
            server.aof_child_pid == -1 &&
            server.aof_rewrite_perc &&
            server.aof_current_size > server.aof_rewrite_min_size)
        {
            long long base = server.aof_rewrite_base_size ?
                server.aof_rewrite_base_size : 1;
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }
    }
    ...
}

這裏的操作跟background RDB備份非常相似, 由主進程啓動一個子進程, 由子進程完成相關的重寫操作rewriteAppendOnlyFile; 在重寫操作進行過程中,主進程新的修改數據庫的操作被記錄在server.aof_buf裏面, 當該buffer滿之後, 寫入到aof_rewrite_buf_blocks 列表內,同時會產生一個寫入操作信號, 通知重寫子進程重寫期間產生的所有操作; 子進程與父進程的通信也是通過pipe的方式傳遞增量信息與ack信息:
server.aof_pipe_write_data_to_child = fds[1];
server.aof_pipe_read_data_from_parent = fds[0];
server.aof_pipe_write_ack_to_parent = fds[3];
server.aof_pipe_read_ack_from_child = fds[2];
server.aof_pipe_write_ack_to_child = fds[5];
server.aof_pipe_read_ack_from_parent = fds[4];

void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
   listNode *ln = listLast(server.aof_rewrite_buf_blocks);
   aofrwblock *block = ln ? ln->value : NULL;

   while(len) {
       /* If we already got at least an allocated block, try appending
        * at least some piece into it. */
       if (block) {
           unsigned long thislen = (block->free < len) ? block->free : len;
           if (thislen) {  /* The current block is not already full. */
               memcpy(block->buf+block->used, s, thislen);
               block->used += thislen;
               block->free -= thislen;
               s += thislen;
               len -= thislen;
           }
       }

       if (len) { /* First block to allocate, or need another block. */
           int numblocks;

           block = zmalloc(sizeof(*block));
           block->free = AOF_RW_BUF_BLOCK_SIZE;
           block->used = 0;
           listAddNodeTail(server.aof_rewrite_buf_blocks,block);

           /* Log every time we cross more 10 or 100 blocks, respectively
            * as a notice or warning. */
           numblocks = listLength(server.aof_rewrite_buf_blocks);
           if (((numblocks+1) % 10) == 0) {
               int level = ((numblocks+1) % 100) == 0 ? LL_WARNING :
                                                        LL_NOTICE;
               serverLog(level,"Background AOF buffer size: %lu MB",
                   aofRewriteBufferSize()/(1024*1024));
           }
       }
   }

   /* Install a file event to send data to the rewrite child if there is
    * not one already. */
   if (aeGetFileEvents(server.el,server.aof_pipe_write_data_to_child) == 0) {
       aeCreateFileEvent(server.el, server.aof_pipe_write_data_to_child,
           AE_WRITABLE, aofChildWriteDiffData, NULL);
   }
}

備份數據的加載

在main函數內loadDataFromDisk來將備份數據加載到內存中, 如果AOF存在通過AOF文件加載, 否則通過RDB加載。

void loadDataFromDisk(void) {
    long long start = ustime();
    if (server.aof_state == AOF_ON) {
        if (loadAppendOnlyFile(server.aof_filename) == C_OK)
            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    } else {
        rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
        if (rdbLoad(server.rdb_filename,&rsi) == C_OK) {
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章