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可能會停止為客戶端服務幾毫秒甚至一秒。
四、什麼是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效能高。
五、什麼是AOF瘦身
根據AOF檔案特性,會將寫命令都寫到檔案中,所以隨著時間的推移,AOF檔案將越來越大,可能會導致磁碟資源耗盡。因此有AOF瘦身機制,將根據一些限制觸發重寫AOF檔案,這個過程是安全的。
當觸發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; .. }