1. 程式人生 > 資料庫 >redis之持久化

redis之持久化

一、什麼是持久化

    眾所周知,redis是記憶體的鍵值對快取資料庫,記憶體特性就是一旦斷電,或者程序重啟,記憶體中的資料就消失了,為了讓程序重啟過後資料能快速恢復,所以誕生了持久化。

持久化的過程就是將記憶體中的資料經過編碼壓縮後寫磁碟,後續重啟後加載磁碟檔案進行恢復。

二、redis提供了什麼方式的持久化

redis提供了三種方式的持久化方案:

  • RDB 

  • AOF 
  • RDB + AOF 混合使用。

三、什麼是RDB

RDB(Redis DataBase) 指redis在某個時間點的全量資料集。通過fork建立子程序的方式建立時間點的資料集快照。藉助系統建立子程序的機制,生成了一份完整資料的“副本”。然後將資料副本進行編碼後寫入到本地檔案中。

3.1 rdb優勢

  • rdb是一個非常緊湊的單檔案的某時間點的資料集。非常適合備份(全量備份)。
  • 非常適合災備恢復,簡單的一個壓縮檔案,可以方便的傳輸到資料中心等。
  • 最大限度的提高redis效能。redis worker程序只需要fork一個程序,主程序不會去操作磁碟I/O。
  • 與aof相比,rdb在重啟大資料集時更快。

3.2 rdb劣勢

  • rdb不宜頻繁操作,間隔時間不宜太端,導致在redis異常停止時,如果要儘量少的資料丟失,則rdb不適合。因為快照時間間隔至少是分鐘級的,丟的資料更多。
  • rdb需要fork建立程序。當資料集很大,程序建立時間會很耗時,如果資料很大並且cpu效能不太好,redis可能會停止為客戶端服務幾毫秒甚至一秒。


QQ截圖20210107223142.png


四、什麼是AOF

AOF(Append Only File)只追加寫檔案,將執行的所有會修改資料的命令都寫入到aof檔案中。重啟時,載入aof檔案,將命令一條一條的進行執行,慢慢恢復資料。

4.1 aof優勢

  • 使用aof更加可靠:可以使用不同的fsync策略:完全沒有fsync,每秒fsync,每個查詢fsync。使用預設策略fsync時,每秒的寫入效能仍然很高(fsync是使用後臺執行緒執行的,並且當沒有fsync進行時,主執行緒將嘗試執行寫入操作。),但是您只能損失一秒鐘的寫入時間。
  • aof日誌是僅追加的日誌,因此,如果斷電,則不會出現尋道或損壞問題。即使由於某種原因(磁碟已滿或其他原因)以半寫命令結束日誌,redis-check-aof工具也可以輕鬆修復它。
  • Redis太大時,Redis能夠在後臺自動重寫AOF。重寫是完全安全的,因為Redis繼續追加到舊檔案時,會生成一個全新的檔案,其中包含建立當前資料集所需的最少操作集,一旦準備好第二個檔案,Redis會切換這兩個檔案並開始追加到新的那一個。
  • aof以易於理解和解析的格式包含所有操作的日誌。您甚至可以輕鬆匯出AOF檔案。例如,即使您使用FLUSHALL命令重新整理了所有錯誤檔案,如果在此期間未執行任何日誌重寫操作,您仍然可以儲存資料集,只是停止伺服器,刪除最新命令,然後重新啟動Redis。

4.2 aof劣勢

  • 相同的資料情況下,aof檔案通常比rdb檔案大。
  • 根據不同的fsync策略,可能效能沒有rdb效能高。

QQ截圖20210107223648.png

五、什麼是AOF瘦身

根據AOF檔案特性,會將寫命令都寫到檔案中,所以隨著時間的推移,AOF檔案將越來越大,可能會導致磁碟資源耗盡。因此有AOF瘦身機制,將根據一些限制觸發重寫AOF檔案,這個過程是安全的。

redis2.png

當觸發aof瘦身(重寫)機制時,首先會使用和rdb相同的方式,fork建立一個子程序,所有的操作都到子程序裡程序操作,防止阻塞服務響應。

  • (1)建立fork子程序,在子程序中進行記憶體檔案編碼寫入到新的aof檔案中。

  • 父程序繼續處理使用者請求,當有寫命令時,(2)在將命令寫入老的aof檔案的同時,(3)也將命令寫入快取佇列中,(4)然後通過管道1傳送給子程序;父程序繼續處理請求。

  • 子程序在將記憶體中的資料寫完後,(5)管道1中讀取父程序傳送過來的新命令,如果這樣一直接收下去就無法結束整個重寫過程,所以這裡只會接收一個特定的時間的資料,(6)然後通過管道2給父程序通知停止傳送新命令。

  • (7)父程序從管道2中讀取讀取到子程序的停止傳送命令請求,設定標誌,後續不再發送命令資料,(8)然後通過管道3傳送確認標誌給子程序,通知子程序自己已經停止傳送。

  • (9)子程序從管道3中獲取到父程序的確認資訊,然後子程序就可以放心的開始後續操作,(10)再次從管道1中讀取資料(在父子程序互動時間內可能還有一些資料),保重管道1中的所有資料都讀取完

  • 子程序將所有從父程序接收到的命令追加寫入到新aof檔案中,然後子程序正常結束。

  • 父程序在子程序狀態檢測時,發現子程序已經正常結束,(11)則將快取佇列中剩餘的命令全部追加寫入到新的aof檔案中

  • 父程序切換老的aof和新的aof檔案,後續使用新的aof檔案


note:

  • 將新命令通過管道方式傳送給子程序,為了儘量減少主程序的磁碟IO,因為最後會在主程序將在重寫期間生成的寫命令追加到新的aof檔案中,因單執行緒執行特性,如果寫磁碟太耗時將導致阻塞其他請求處理

  • 其中管道有三個,每個階段使用的管道不同,防止資料的混淆

  • rdb+aof的方式是高版本 引入的,是(1)中寫入的資料是直接的rdb編碼的資料,而不是命令資料,然後後面追加命令,(即aof檔案開頭部分是rdb資訊,後名是aof資訊),在重啟載入時能加快載入速度。


六、簡單配置及使用

6.1 rdb

6.1.1 手動備份

同步命令,將導致無法響應其他請求,因為命令是單執行緒執行的,所以在執行這個命令時,將無法響應其他請求,根據具體場景使用。

save


非同步命令,立刻返回,然後建立程序進行處理,不阻塞其他請求。

bgsave


6.1.2 自動備份

可以通過配置檔案進行配置,配置如下,60秒內至少改變1000次,將觸發備份

save 60 1000
#關閉rdb持久化
save ""


6.2 aof

6.2.1 配置開啟

redis1.1版本開始支援aof, 開啟後將所有接收到的對資料改變的命令追加到檔案中。

appendonly yes


6.2.2 日誌重寫

隨著redis的使用,aof將不斷的變大。比如增加一個計數器100次,記憶體中只有一個最終值,而aof中有100次記錄,在使用aof重建資料時,99次都沒有必要,直接寫最終值即可。

(1) 手動重寫

低版本的redis需要通過命令來觸發。

bgrewriteaof


(2) 自動重寫

redis2.4開始即可自動觸發aof重寫

#比上一次重寫大小增長100%
auto-aof-rewrite-percentage 100
#大小達到64mb
auto-aof-rewrite-min-size 64mb


(3) aof檔案的可靠性

通過配置fsync策略,來確保資料的可靠性。

  • 每追加一條日誌,將呼叫fsync進行磁碟的同步。 效率非常慢,但是非常安全。
appendfsync always
  • 每秒同步一次。已經足夠快,可能會丟失一秒的資料。
appendfsync everysec
  • 從不同步,依賴作業系統的重新整理磁碟的時間,一般30秒
appendfsync no


七、程式碼邏輯

7.1 啟動時從磁碟上恢復資料

void loadDataFromDisk(void) {
    ..
    //開啟aof
    if (server.aof_state == AOF_ON) {
        //載入aof
        if (loadAppendOnlyFile(server.aof_filename) == C_OK)
            serverLog(LL_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);
    } else {
        //未開啟aof, 嘗試載入rdb
        if (rdbLoad(server.rdb_filename,&rsi,RDBFLAGS_NONE) == C_OK) 
        ...
}

7.1 持久化相關命令表

命令表如下,根據接收到的命令進行查表執行

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},

    {"bgrewriteaof",bgrewriteaofCommand,1,
     "admin no-script",
     0,NULL,0,0,0,0,0,0},
    ...
}

save命令直接呼叫rdbSave函式,同步操作,將會阻塞其他客戶端的響應

void saveCommand(client *c) {
     ...
    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

int rdbSave(char *filename, rdbSaveInfo *rsi) {
    ...
    //建立臨時檔案
    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
   
     ...
     //開始儲存
     rdbSaveRio(&rdb,&error,RDBFLAGS_NONE,rsi)
     ...

    //重新整理
    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    //將臨時檔案修改為備份檔案
    rename(tmpfile,filename)
    ...
}

bgsave 通過fork子程序方式,子程序呼叫rdbSave,父程序繼續響應其他客戶端

void bgsaveCommand(client *c) {
    ...
    } else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {

    ...
    //建立程序
    if ((childpid = redisFork()) == 0) {
        ...
        retval = rdbSave(filename,rsi);
        ...
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent */
        ...
        //父程序直接返回
        serverLog(LL_NOTICE,"Background saving started by pid %d",childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_pid = childpid;
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        return C_OK;
    }
    return C_OK; /* unreached */
}


7.2 自動觸發的持久化

rdb配置相關

#15分鐘至少1個改變
save 900 1 

#5分鐘至少10個改變
save 300 10

#1分鐘至少1萬個改變
save 60 10000

#指定檔名
dbfilename dump.rdb

#指定儲存路徑
dir ./

#是否壓縮
rdbcompression yes

#是否進行校驗,不校驗,這校驗頭為0,載入時跳過校驗步驟
rdbchecksum yes

rdb觸發邏輯

//初始化部分,載入相關配置
initServerConfig() {
    ...
    //預設配置
    appendServerSaveParams(60*60,1);  /* save after 1 hour and 1 change */
    appendServerSaveParams(300,100);  /* save after 5 minutes and 100 changes */
    appendServerSaveParams(60,10000); /* save after 1 minute and 10000 changes */
}

//從檔案中載入配置
loadServerConfig(){
    ...
    //先從從檔案中將配置讀取出來
    ...
    //解析字串
    loadServerConfigFromString()
}

loadServerConfigFromString() {
    ...
    else if (!strcasecmp(argv[0],"save")) {
            if (argc == 3) {
                int seconds = atoi(argv[1]);
                int changes = atoi(argv[2]);
                if (seconds < 1 || changes < 0) {
                    err = "Invalid save parameters"; goto loaderr;
                }
                appendServerSaveParams(seconds,changes);
            } else if (argc == 2 && !strcasecmp(argv[1],"")) {
                //關閉rdb
                resetServerSaveParams();
            }
        ...
}

//定時任務,進行rdb持久化條件檢測
serverCron() {
    ....
    //遍歷save配置
    for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

           //如果滿足在規定時間內超過規定改變次數, 並且距離上次備份大於5秒或者上次備份失敗
            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;
            }
        }
}


7.3 開啟aof時

aof配置相關

#開關
appendonly yes

#檔名
appendfilename "appendonly.aof"

#同步模式
# appendfsync always 
appendfsync everysec
# appendfsync no

#自動重寫規則,
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

aof處理邏輯,首先會將命令寫入快取中

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    ...
    //生成日誌命令
    buf = catAppendOnlyGenericCommand(buf,argc,argv);
    ...
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
    ...

    sdsfree(buf);
}

然後一次性寫入檔案中,然後根據刷磁碟策略進行刷磁碟。

beforeSleep(){
    ...
    flushAppendOnlyFile(0)
    ...
}

void flushAppendOnlyFile(int force) {
    ...
    nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
    ...

       //每條日誌都要刷磁碟
    if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
        ...
        redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
        ...
        server.aof_fsync_offset = server.aof_current_size;
        server.aof_last_fsync = server.unixtime;
      
      //每秒鐘刷磁碟一次
    } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
                server.unixtime > server.aof_last_fsync)) {
         //如果上次的刷盤還沒有結束,只有等待下一次了       
        if (!sync_in_progress) {
            aof_background_fsync(server.aof_fd);
            server.aof_fsync_offset = server.aof_current_size;
        }
        server.aof_last_fsync = server.unixtime;
    }
}

//每秒刷盤,是通過後臺執行緒完成的,將任務分發下去即可。
void aof_background_fsync(int fd) {
    bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL);
}
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
    struct bio_job *job = zmalloc(sizeof(*job));

    job->time = time(NULL);
    job->arg1 = arg1;
    job->arg2 = arg2;
    job->arg3 = arg3;
    pthread_mutex_lock(&bio_mutex[type]);
    listAddNodeTail(bio_jobs[type],job);
    bio_pending[type]++;
    pthread_cond_signal(&bio_newjob_cond[type]);
    pthread_mutex_unlock(&bio_mutex[type]);
}

7.4檢測aof重寫邏輯

serverCron()
{
    ...
      /* Trigger an AOF rewrite if needed. */
      //1. 首先開啟了aof
      //2.沒有其他子程序(同時只能有一個子程序,不管是rdb,rewriteaof,replica導致生成的子程序)
      //3.配置了增長比例
      //4.當前aof檔案大於配置的aof最大值
        if (server.aof_state == AOF_ON &&
            !hasActiveChildProcess() &&
            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;
            //計算增長率,大於則進行aof重寫
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }
        ...
}


int rewriteAppendOnlyFileBackground(void) {
    pid_t childpid;
    //同時只能有一個子程序在處理。
    if (hasActiveChildProcess()) return C_ERR;
   
    //建立管道,三個管道,並且父程序註冊管道2(讀取子程序傳送停止父程序傳送資料通知)的讀回撥函式
    if (aofCreatePipes() != C_OK) return C_ERR;
   
   //建立子程序
    if ((childpid = redisFork()) == 0) {
        char tmpfile[256];

        /* Child */
        redisSetProcTitle("redis-aof-rewrite");
        redisSetCpuAffinity(server.aof_rewrite_cpulist);
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
        if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
            exitFromChild(0);
        } else {
            exitFromChild(1);
        }
    } else {
        /* Parent */
        server.aof_child_pid = childpid;
        ...
        return C_OK;
    }
    return C_OK; /* unreached */
}

子程序的處理邏輯

int rewriteAppendOnlyFile(char *filename) {
    rio aof;
    FILE *fp;
    char tmpfile[256];
    char byte;
    ...
    snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
    fp = fopen(tmpfile,"w");
    ...
    server.aof_child_diff = sdsempty();
   
   //兩種方式
    if (server.aof_use_rdb_preamble) {
        //rdb+aof
        if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) { 
            goto werr;
        }
    } else {
        //aof
        if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
    }

    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    /**  這裡新的aof已經寫完,下面的是接收父程序傳送過來的新資料**/
    
    
    /* Read again a few times to get more data from the parent.
     * We can't read forever (the server may receive data from clients
     * faster than it is able to send data to the child), so we try to read
     * some more data in a loop as soon as there is a good chance more data
     * will come. If it looks like we are wasting time, we abort (this
     * happens after 20 ms without new data). */
     
     //接收父程序新的日誌、
    int nodata = 0;
    mstime_t start = mstime();
    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. */
         //將接收的資料寫入 server.aof_child_diff
         //從管道中讀取資料(管道1)
        aofReadDiffFromParent();
    }

    //接收一段時間後,停止接收
    
    /* Ask the master to stop sending diffs. */
    //告訴父程序不要傳送資料了(管道2)
    if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr;
    if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK)
        goto werr;
        
    /* We read the ACK from the server using a 10 seconds timeout. Normally
     * it should reply ASAP, but just in case we lose its reply, we are sure
     * the child will eventually get terminated. */
     //接收父程序的確認(管道3)
    if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 ||
        byte != '!') goto werr;
    serverLog(LL_NOTICE,"Parent agreed to stop sending diffs. Finalizing AOF...");

    /* Read the final diff if any. */
    //再次讀取資料(管道1),確保將父程序傳送的資料都接收完
    aofReadDiffFromParent();

    /* Write the received diff to the file. */
   //將新的資料寫入檔案
    if (rioWrite(&aof,server.aof_child_diff,sdslen(server.aof_child_diff)) == 0)
        goto werr;

    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
     //重新命名為新的aof檔案
    if (rename(tmpfile,filename) == -1) {
        serverLog(LL_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        stopSaving(0);
        return C_ERR;
    }
    
     ...
    return C_OK;

werr:
    serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
    fclose(fp);
    unlink(tmpfile);
    stopSaving(0);
    return C_ERR;
}


主程序邏輯

主程序在處理請求的同時寫老的aof檔案,同時寫緩衝區,然後傳送到子程序(管道1)

void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
    sds buf = sdsempty();
    ...
    //生成日誌命令
    buf = catAppendOnlyGenericCommand(buf,argc,argv);
    
    ...
    if (server.aof_state == AOF_ON)
        server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
    ...
    
    //正在重寫aof, 將當前日誌寫入到佇列中
    if (server.aof_child_pid != -1)
        aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));


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

    //將資料寫入快取區中
    while(len) {
         ...
         listAddNodeTail(server.aof_rewrite_buf_blocks,block);
         ...
    }

    /* Install a file event to send data to the rewrite child if there is
     * not one already. */
     //註冊管道1寫事件,回撥函式將佇列資料傳送給子程序
    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);
    }
}

父程序將快取佇列中的資料傳送給子程序

void aofChildWriteDiffData(aeEventLoop *el, int fd, void *privdata, int mask) {
    listNode *ln;
    aofrwblock *block;
    ssize_t nwritten;
    ...
    while(1) {
        ln = listFirst(server.aof_rewrite_buf_blocks);
        block = ln ? ln->value : NULL;
        
        //server.aof_stop_sending_diff 為子程序通知父程序停止傳送資料標誌
        if (server.aof_stop_sending_diff || !block) {
            aeDeleteFileEvent(server.el,server.aof_pipe_write_data_to_child,
                              AE_WRITABLE);
            return;
        }
        
        //將資料寫入管道1
        if (block->used > 0) {
            nwritten = write(server.aof_pipe_write_data_to_child,
                             block->buf,block->used);
            if (nwritten <= 0) return;
            memmove(block->buf,block->buf+nwritten,block->used-nwritten);
            block->used -= nwritten;
            block->free += nwritten;
        }
        if (block->used == 0) listDelNode(server.aof_rewrite_buf_blocks,ln);
    }
}

父程序獲取到子程序停止傳送資料通知,併發送確認訊息

void aofChildPipeReadable(aeEventLoop *el, int fd, void *privdata, int mask) {
    char byte;
   ...
    //從管道2中讀取到標誌
    if (read(fd,&byte,1) == 1 && byte == '!') {
      //設定停止傳送標誌
        server.aof_stop_sending_diff = 1;
        
        //給重寫程序傳送父程序已經收到訊息,寫入(管道3)
        if (write(server.aof_pipe_write_ack_to_child,"!",1) != 1) {
            /* If we can't send the ack, inform the user, but don't try again
             * since in the other side the children will use a timeout if the
             * kernel can't buffer our write, or, the children was
             * terminated. */
            serverLog(LL_WARNING,"Can't send ACK to AOF child: %s",
                strerror(errno));
        }
    }
    /* Remove the handler since this can be called only one time during a
     * rewrite. */
    aeDeleteFileEvent(server.el,server.aof_pipe_read_ack_from_child,AE_READABLE);
}

主程序檢測到子程序已經結束,然後寫入剩下的快取命令,切換檔案

checkChildrenDone(){
    ...
     pid = wait3(&statloc,WNOHANG,NULL)
    ...
    
    if (pid == server.aof_child_pid) {   
        backgroundRewriteDoneHandler(exitcode,bysignal);
    }
}

void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
    //正常結束
    if (!bysignal && exitcode == 0) {
        
        ....
        snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
            (int)server.aof_child_pid);
        newfd = open(tmpfile,O_WRONLY|O_APPEND);
        ...

        //執行未傳送完的新日誌
        if (aofRewriteBufferWrite(newfd) == -1) {
           ...
            close(newfd);
            goto cleanup;
        }
         
         ...
         
        if (rename(tmpfile,server.aof_filename) == -1) {
          
           
            close(newfd);
            if (oldfd != -1) close(oldfd);
            goto cleanup;
        }
     
        if (server.aof_fd == -1) {
            /* AOF disabled, we don't need to set the AOF file descriptor
             * to this new file, so we can close it. */
            close(newfd);
        } else {
            //交換新的fd
            /* AOF enabled, replace the old fd with the new one. */
            oldfd = server.aof_fd;
            server.aof_fd = newfd;
            
            //根據相應的策略配置進行相應的刷盤
            if (server.aof_fsync == AOF_FSYNC_ALWAYS)
                redis_fsync(newfd);
            else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
                aof_background_fsync(newfd);
           
           //獲取檔案屬性,得到server.aof_current_size;
            aofUpdateCurrentSize();
            server.aof_rewrite_base_size = server.aof_current_size;
            server.aof_fsync_offset = server.aof_current_size;

            /* Clear regular AOF buffer since its contents was just written to
             * the new AOF from the background rewrite buffer. */
            sdsfree(server.aof_buf);
            server.aof_buf = sdsempty();
        }

        /* Asynchronously close the overwritten AOF. */
        if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);

    //異常結束
    } else if (!bysignal && exitcode != 0) {
        server.aof_lastbgrewrite_status = C_ERR;

        serverLog(LL_WARNING,
            "Background AOF rewrite terminated with error");
            
     //被訊號終止
    } else {
        /* SIGUSR1 is whitelisted, so we have a way to kill a child without
         * tirggering an error condition. */
        if (bysignal != SIGUSR1)
            server.aof_lastbgrewrite_status = C_ERR;

        serverLog(LL_WARNING,
            "Background AOF rewrite terminated by signal %d", bysignal);
    }

cleanup:
    ...
    aofClosePipes();
    ...
    //刪除臨時檔案
    aofRemoveTempFile(server.aof_child_pid);
    server.aof_child_pid = -1;
    server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;
    server.aof_rewrite_time_start = -1;
   ..
}