Redis深度歷險-AOF持久化 Redis深度歷險-AOF持久化

Redis深度歷險-AOF持久化

Redis提供兩種持久化方式AOF和RDB,RDB是快照形式持久化全量數據、AOF是增量持久化記錄執行命令

AOF原理

struct redisServer {
  ........
    sds aof_buf;            //AOF緩衝區

  AOF持久化的是Redis執行的寫入命令,Redis會將執行的寫入命令放入AOF緩衝區中;而在Redis的定時任務server.c/serverCron中將數據寫入到磁盤中

  在恢復數據時按照順序依次執行即可恢復數據。

AOF配置

  • appendonly yes:是否開啓AOF持久化
  • appendfilename appendonly.aof:文件名
  • appendfsync:磁盤寫入策略,主要是因爲文件IO有緩衝區可能數據沒有真正寫入到文件
    • always:每次寫入磁盤,性能較差
    • everysec:每秒寫入一次
    • no:系統處理緩存回寫

  在這裏面影響最大的是磁盤寫入策略,因爲調用write寫入的是系統緩衝區而沒有真正落盤,如果系統發生宕機則會造成數據丟失,不過Linux提供fsyncfdatasync等接口將數據刷入硬盤中

  磁盤寫入策略就是控制落盤的時機,需要根據系統需求在數據和性能中作出取捨

  當然,調用write接口也是註冊事件到Redis的事件循環中一併處理的

AOF實現

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    ........
    
    //將命令寫入到aof緩衝區中
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));

    //如果正在進程AOF重寫時,則同樣將輸入發送給AOF重寫子進程
    if (server.child_type == CHILD_TYPE_AOF)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));

AOF重寫

AOF的問題在於存儲的是明文命令且存在冗餘,文件內容會非常大,在恢復數據時耗時非常長

AOF重寫原理

SET "A" "B"
DEL "A"
SET "A" "C"
SET "A" "D"

類似於這樣執行後,Redis中實際上只有A這樣一個鍵,AOF中存儲的四條命令實際上存在冗餘;AOF重寫的原理就是根據內存中的數據將AOF文件重寫爲:

SET "A" "D"

在恢復數據時最終得出的結果時一致的

AOF重寫實現

執行bgrewriteaof命令即可進行AOF重寫

int rewriteAppendOnlyFileBackground(void) {
    pid_t childpid;

    if (hasActiveChildProcess()) return C_ERR;
    if (aofCreatePipes() != C_OK) return C_ERR;
    //創建一個子進程來執行重寫指令
    if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
        char tmpfile[256];
                
        //並不是在AOF文件中直接重寫,而是重寫到一個新的文件最終原子行替換
        redisSetProcTitle("redis-aof-rewrite");
        redisSetCpuAffinity(server.aof_rewrite_cpulist);
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
            exitFromChild(0);
        } else {
            exitFromChild(1);
        }
    } else {
        /* Parent */
        if (childpid == -1) {
            serverLog(LL_WARNING,
                "Can't rewrite append only file in background: fork: %s",
                strerror(errno));
            aofClosePipes();
            return C_ERR;
        }
        serverLog(LL_NOTICE,
            "Background append only file rewriting started by pid %ld",(long) childpid);
        server.aof_rewrite_scheduled = 0;
        server.aof_rewrite_time_start = time(NULL);

        server.aof_selected_db = -1;
        replicationScriptCacheFlush();
        return C_OK;
    }
    return C_OK; /* unreached */
}
  • AOF重寫是不會阻塞Redis執行的,因爲創建了子進程來執行
  • AOF重寫也是不會影響AOF文件的,是在一個臨時文件中寫入數據的,最終用rename來替換
int rewriteAppendOnlyFileRio(rio *aof) {
    dictIterator *di = NULL;
    dictEntry *de;
    size_t processed = 0;
    int j;
    long key_count = 0;
    long long updated_time = 0;
        
    //逐個遍歷所有db
    for (j = 0; j < server.dbnum; j++) {
        char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
        redisDb *db = server.db+j;
        dict *d = db->dict;
        if (dictSize(d) == 0) continue;
        di = dictGetSafeIterator(d);

        //在db切換時記錄select命令
        if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
        if (rioWriteBulkLongLong(aof,j) == 0) goto werr;

        //直接遍歷內存中的字典
        while((de = dictNext(di)) != NULL) {
            sds keystr;
            robj key, *o;
            long long expiretime;

            keystr = dictGetKey(de);
            o = dictGetVal(de);
            initStaticStringObject(key,keystr);

            expiretime = getExpire(db,&key);

            //存儲命令、key、value
            if (o->type == OBJ_STRING) {
                /* Emit a SET command */
                char cmd[]="*3\r\n$3\r\nSET\r\n";
                if (rioWrite(aof,cmd,sizeof(cmd)-1) == 0) goto werr;
                /* Key and value */
                if (rioWriteBulkObject(aof,&key) == 0) goto werr;
                if (rioWriteBulkObject(aof,o) == 0) goto werr;
    ........

AOF重寫直接就是遍歷所有數據庫的所有鍵值,存儲到AOF文件中

AOF重寫緩衝區

AOF重寫過程中主進程接收到新的指令就會放入到AOF重寫緩衝區中

子進程
int rewriteAppendOnlyFile(char *filename) {
  ........
      while(mstime()-start < 1000 && nodata < 20) {
        if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0)
        {
            nodata++;
            continue;
        }
        nodata = 0; /* Start counting from zero, we stop on N *contiguous*
                       timeouts. */
        aofReadDiffFromParent();
    }

同時AOF重寫子進程會接收到重寫過程中Redis進程執行的指令並同樣記錄到文件中

父進程
void aofRewriteBufferAppend(unsigned char *s, unsigned long len) {
    listNode *ln = listLast(server.aof_rewrite_buf_blocks);
    ........

重寫緩衝區是以鏈表實現的,一段一段的通過事件循環發送給子進程;最終如果子進程結束後還有剩餘,由主進程將數據追加寫入到重寫後的AOF文件中

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章