Redis數據持久化機制AOF原理分析一---轉


http://blog.csdn.net/acceptedxukai/article/details/18136903

http://blog.csdn.net/acceptedxukai/article/details/18181563

本文所引用的源碼全部來自Redis2.8.2版本。

Redis AOF數據持久化機制的實現相關代碼是redis.c, redis.h, aof.c, bio.c, rio.c, config.c

在閱讀本文之前請先閱讀Redis數據持久化機制AOF原理分析之配置詳解文章,了解AOF相關參數的解析,文章鏈接

http://blog.csdn.net/acceptedxukai/article/details/18135219

轉載請注明,文章出自http://blog.csdn.net/acceptedxukai/article/details/18136903

下面將介紹AOF數據持久化機制的實現

 

Server啟動加載AOF文件數據

 

Server啟動加載AOF文件數據的執行步驟為:main() -> initServerConfig() -> loadServerConfig() -> initServer() -> loadDataFromDisk()。initServerConfig()主要為初始化默認的AOF參數配置;loadServerConfig()加載配置文件redis.conf中AOF的參數配置,覆蓋Server的默認AOF參數配置,如果配置appendonly on,那么AOF數據持久化功能將被激活,server.aof_state參數被設置為REDIS_AOF_ON;loadDataFromDisk()判斷server.aof_state == REDIS_AOF_ON,結果為True就調用loadAppendOnlyFile函數加載AOF文件中的數據,加載的方法就是讀取AOF文件中數據,由於AOF文件中存儲的數據與客戶端發送的請求格式相同完全符合Redis的通信協議,因此Server創建偽客戶端fakeClient,將解析后的AOF文件數據像客戶端請求一樣調用各種指令,cmd->proc(fakeClient),將AOF文件中的數據重現到Redis Server數據庫中。

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Function called at startup to load RDB or AOF file in memory. */  
  2. void loadDataFromDisk(void) {  
  3.     long long start = ustime();  
  4.     if (server.aof_state == REDIS_AOF_ON) {  
  5.         if (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)  
  6.             redisLog(REDIS_NOTICE,"DB loaded from append only file: %.3f seconds",(float)(ustime()-start)/1000000);  
  7.     } else {  
  8.         if (rdbLoad(server.rdb_filename) == REDIS_OK) {  
  9.             redisLog(REDIS_NOTICE,"DB loaded from disk: %.3f seconds",  
  10.                 (float)(ustime()-start)/1000000);  
  11.         } else if (errno != ENOENT) {  
  12.             redisLog(REDIS_WARNING,"Fatal error loading the DB: %s. Exiting.",strerror(errno));  
  13.             exit(1);  
  14.         }  
  15.     }  
  16. }  

Server首先判斷加載AOF文件是因為AOF文件中的數據要比RDB文件中的數據要新。

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. int loadAppendOnlyFile(char *filename) {  
  2.     struct redisClient *fakeClient;  
  3.     FILE *fp = fopen(filename,"r");  
  4.     struct redis_stat sb;  
  5.     int old_aof_state = server.aof_state;  
  6.     long loops = 0;  
  7.   
  8.     //redis_fstat就是fstat64函數,通過fileno(fp)得到文件描述符,獲取文件的狀態存儲於sb中,  
  9.     //具體可以參考stat函數,st_size就是文件的字節數  
  10.     if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {  
  11.         server.aof_current_size = 0;  
  12.         fclose(fp);  
  13.         return REDIS_ERR;  
  14.     }  
  15.   
  16.     if (fp == NULL) {//打開文件失敗  
  17.         redisLog(REDIS_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));  
  18.         exit(1);  
  19.     }  
  20.   
  21.     /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI 
  22.      * to the same file we're about to read. */  
  23.     server.aof_state = REDIS_AOF_OFF;  
  24.   
  25.     fakeClient = createFakeClient(); //建立偽終端  
  26.     startLoading(fp); // 定義於 rdb.c ,更新服務器的載入狀態  
  27.   
  28.     while(1) {  
  29.         int argc, j;  
  30.         unsigned long len;  
  31.         robj **argv;  
  32.         char buf[128];  
  33.         sds argsds;  
  34.         struct redisCommand *cmd;  
  35.   
  36.         /* Serve the clients from time to time */  
  37.         // 有間隔地處理外部請求,ftello()函數得到文件的當前位置,返回值為long  
  38.         if (!(loops++ % 1000)) {  
  39.             loadingProgress(ftello(fp));//保存aof文件讀取的位置,ftellno(fp)獲取文件當前位置  
  40.             aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);//處理事件  
  41.         }  
  42.         //按行讀取AOF數據  
  43.         if (fgets(buf,sizeof(buf),fp) == NULL) {  
  44.             if (feof(fp))//達到文件尾EOF  
  45.                 break;  
  46.             else  
  47.                 goto readerr;  
  48.         }  
  49.         //讀取AOF文件中的命令,依照Redis的協議處理  
  50.         if (buf[0] != '*'goto fmterr;  
  51.         argc = atoi(buf+1);//參數個數  
  52.         if (argc < 1) goto fmterr;  
  53.   
  54.         argv = zmalloc(sizeof(robj*)*argc);//參數值  
  55.         for (j = 0; j < argc; j++) {  
  56.             if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;  
  57.             if (buf[0] != '$'goto fmterr;  
  58.             len = strtol(buf+1,NULL,10);//每個bulk的長度  
  59.             argsds = sdsnewlen(NULL,len);//新建一個空sds  
  60.             //按照bulk的長度讀取  
  61.             if (len && fread(argsds,len,1,fp) == 0) goto fmterr;  
  62.             argv[j] = createObject(REDIS_STRING,argsds);  
  63.             if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF 跳過\r\n*/  
  64.         }  
  65.   
  66.         /* Command lookup */  
  67.         cmd = lookupCommand(argv[0]->ptr);  
  68.         if (!cmd) {  
  69.             redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);  
  70.             exit(1);  
  71.         }  
  72.         /* Run the command in the context of a fake client */  
  73.         fakeClient->argc = argc;  
  74.         fakeClient->argv = argv;  
  75.         cmd->proc(fakeClient);//執行命令  
  76.   
  77.         /* The fake client should not have a reply */  
  78.         redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);  
  79.         /* The fake client should never get blocked */  
  80.         redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);  
  81.   
  82.         /* Clean up. Command code may have changed argv/argc so we use the 
  83.          * argv/argc of the client instead of the local variables. */  
  84.         for (j = 0; j < fakeClient->argc; j++)  
  85.             decrRefCount(fakeClient->argv[j]);  
  86.         zfree(fakeClient->argv);  
  87.     }  
  88.   
  89.     /* This point can only be reached when EOF is reached without errors. 
  90.      * If the client is in the middle of a MULTI/EXEC, log error and quit. */  
  91.     if (fakeClient->flags & REDIS_MULTI) goto readerr;  
  92.   
  93.     fclose(fp);  
  94.     freeFakeClient(fakeClient);  
  95.     server.aof_state = old_aof_state;  
  96.     stopLoading();  
  97.     aofUpdateCurrentSize(); //更新server.aof_current_size,AOF文件大小  
  98.     server.aof_rewrite_base_size = server.aof_current_size;  
  99.     return REDIS_OK;  
  100.     …………  
  101. }  

在前面一篇關於AOF參數配置的博客遺留了一個問題,server.aof_current_size參數的初始化,下面解決這個疑問。

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. void aofUpdateCurrentSize(void) {  
  2.     struct redis_stat sb;  
  3.   
  4.     if (redis_fstat(server.aof_fd,&sb) == -1) {  
  5.         redisLog(REDIS_WARNING,"Unable to obtain the AOF file length. stat: %s",  
  6.             strerror(errno));  
  7.     } else {  
  8.         server.aof_current_size = sb.st_size;  
  9.     }  
  10. }  

redis_fstat是作者對Linux中fstat64函數的重命名,該還是就是獲取文件相關的參數信息,具體可以Google之,sb.st_size就是當前AOF文件的大小。這里需要知道server.aof_fd即AOF文件描述符,該參數的初始化在initServer()函數中

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Open the AOF file if needed. */  
  2.     if (server.aof_state == REDIS_AOF_ON) {  
  3.         server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);  
  4.         if (server.aof_fd == -1) {  
  5.             redisLog(REDIS_WARNING, "Can't open the append-only file: %s",strerror(errno));  
  6.             exit(1);  
  7.         }  
  8.     }  

 

 

至此,Redis Server啟動加載硬盤中AOF文件數據的操作就成功結束了。

 

 

Server數據庫產生新數據如何持久化到硬盤


當客戶端執行Set等修改數據庫中字段的指令時就會造成Server數據庫中數據被修改,這些修改的數據應該被實時更新到AOF文件中,並且也要按照一定的fsync機制刷新到硬盤中,保證數據不會丟失。

 

在上一篇博客中,提到了三種fsync方式:appendfsync always, appendfsync everysec, appendfsync no. 具體體現在server.aof_fsync參數中。

首先看當客戶端請求的指令造成數據被修改,Redis是如何將修改數據的指令添加到server.aof_buf中的。

call() -> propagate() -> feedAppendOnlyFile(),call()函數判斷執行指令后是否造成數據被修改。

feedAppendOnlyFile函數首先會判斷Server是否開啟了AOF,如果開啟AOF,那么根據Redis通訊協議將修改數據的指令重現成請求的字符串,注意在超時設置的處理方式,接着將字符串append到server.aof_buf中即可。該函數最后兩行代碼需要注意,這才是重點,如果server.aof_child_pid != -1那么表明此時Server正在重寫rewrite AOF文件,需要將被修改的數據追加到server.aof_rewrite_buf_blocks鏈表中,等待rewrite結束后,追加到AOF文件中。具體見下面代碼的注釋。

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Propagate the specified command (in the context of the specified database id) 
  2.  * to AOF and Slaves. 
  3.  * 
  4.  * flags are an xor between: 
  5.  * + REDIS_PROPAGATE_NONE (no propagation of command at all) 
  6.  * + REDIS_PROPAGATE_AOF (propagate into the AOF file if is enabled) 
  7.  * + REDIS_PROPAGATE_REPL (propagate into the replication link) 
  8.  */  
  9. void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,  
  10.                int flags)  
  11. {  
  12.     //將cmd指令變動的數據追加到AOF文件中  
  13.     if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)  
  14.         feedAppendOnlyFile(cmd,dbid,argv,argc);  
  15.     if (flags & REDIS_PROPAGATE_REPL)  
  16.         replicationFeedSlaves(server.slaves,dbid,argv,argc);  
  17. }  
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. //cmd指令修改了數據,先將更新的數據寫到server.aof_buf中  
  2. void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {  
  3.     sds buf = sdsempty();  
  4.     robj *tmpargv[3];  
  5.   
  6.     /* The DB this command was targeting is not the same as the last command 
  7.      * we appendend. To issue a SELECT command is needed. */  
  8.     // 當前 db 不是指定的 aof db,通過創建 SELECT 命令來切換數據庫  
  9.     if (dictid != server.aof_selected_db) {  
  10.         char seldb[64];  
  11.   
  12.         snprintf(seldb,sizeof(seldb),"%d",dictid);  
  13.         buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",  
  14.             (unsigned long)strlen(seldb),seldb);  
  15.         server.aof_selected_db = dictid;  
  16.     }  
  17.   
  18.     // 將 EXPIRE / PEXPIRE / EXPIREAT 命令翻譯為 PEXPIREAT 命令  
  19.     if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||  
  20.         cmd->proc == expireatCommand) {  
  21.         /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */  
  22.         buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);  
  23.     }// 將 SETEX / PSETEX 命令翻譯為 SET 和 PEXPIREAT 組合命令  
  24.     else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {  
  25.         /* Translate SETEX/PSETEX to SET and PEXPIREAT */  
  26.         tmpargv[0] = createStringObject("SET",3);  
  27.         tmpargv[1] = argv[1];  
  28.         tmpargv[2] = argv[3];  
  29.         buf = catAppendOnlyGenericCommand(buf,3,tmpargv);  
  30.         decrRefCount(tmpargv[0]);  
  31.         buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);  
  32.     } else {//其他的指令直接追加  
  33.         /* All the other commands don't need translation or need the 
  34.          * same translation already operated in the command vector 
  35.          * for the replication itself. */  
  36.         buf = catAppendOnlyGenericCommand(buf,argc,argv);  
  37.     }  
  38.   
  39.     /* Append to the AOF buffer. This will be flushed on disk just before 
  40.      * of re-entering the event loop, so before the client will get a 
  41.      * positive reply about the operation performed. */  
  42.     // 將 buf 追加到服務器的 aof_buf 末尾,在beforeSleep中寫到AOF文件中,並且根據情況fsync刷新到硬盤  
  43.     if (server.aof_state == REDIS_AOF_ON)  
  44.         server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));  
  45.   
  46.     /* If a background append only file rewriting is in progress we want to 
  47.      * accumulate the differences between the child DB and the current one 
  48.      * in a buffer, so that when the child process will do its work we 
  49.      * can append the differences to the new append only file. */  
  50.     //如果server.aof_child_pid不為1,那就說明有快照進程正在寫數據到臨時文件(已經開始rewrite),  
  51.     //那么必須先將這段時間接收到的指令更新的數據先暫時存儲起來,等到快照進程完成任務后,  
  52.     //將這部分數據寫入到AOF文件末尾,保證數據不丟失  
  53.     //解釋為什么需要aof_rewrite_buf_blocks,當server在進行rewrite時即讀取所有數據庫中的數據,  
  54.     //有些數據已經寫到新的AOF文件,但是此時客戶端執行指令又將該值修改了,因此造成了差異  
  55.     if (server.aof_child_pid != -1)  
  56.         aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));  
  57.     /*這里說一下server.aof_buf和server.aof_rewrite_buf_blocks的區別 
  58.       aof_buf是正常情況下aof文件打開的時候,會不斷將這份數據寫入到AOF文件中。 
  59.       aof_rewrite_buf_blocks 是如果用戶主動觸發了寫AOF文件的命令時,比如 config set appendonly yes命令 
  60.       那么redis會fork創建一個后台進程,也就是當時的數據快照,然后將數據寫入到一個臨時文件中去。 
  61.       在此期間發送的命令,我們需要把它們記錄起來,等后台進程完成AOF臨時文件寫后,serverCron定時任務 
  62.       感知到這個退出動作,然后就會調用backgroundRewriteDoneHandler進而調用aofRewriteBufferWrite函數, 
  63.       將aof_rewrite_buf_blocks上面的數據,也就是diff數據寫入到臨時AOF文件中,然后再unlink替換正常的AOF文件。 
  64.       因此可以知道,aof_buf一般情況下比aof_rewrite_buf_blocks要少, 
  65.       但開始的時候可能aof_buf包含一些后者不包含的前面部分數據。*/  
  66.   
  67.     sdsfree(buf);  
  68. }  

 

 

Server在每次事件循環之前會調用一次beforeSleep函數,下面看看這個函數做了什么工作?

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* This function gets called every time Redis is entering the 
  2.  * main loop of the event driven library, that is, before to sleep 
  3.  * for ready file descriptors. */  
  4. void beforeSleep(struct aeEventLoop *eventLoop) {  
  5.     REDIS_NOTUSED(eventLoop);  
  6.     listNode *ln;  
  7.     redisClient *c;  
  8.   
  9.     /* Run a fast expire cycle (the called function will return 
  10.      * ASAP if a fast cycle is not needed). */  
  11.     if (server.active_expire_enabled && server.masterhost == NULL)  
  12.         activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);  
  13.   
  14.     /* Try to process pending commands for clients that were just unblocked. */  
  15.     while (listLength(server.unblocked_clients)) {  
  16.         ln = listFirst(server.unblocked_clients);  
  17.         redisAssert(ln != NULL);  
  18.         c = ln->value;  
  19.         listDelNode(server.unblocked_clients,ln);  
  20.         c->flags &= ~REDIS_UNBLOCKED;  
  21.   
  22.         /* Process remaining data in the input buffer. */  
  23.         //處理客戶端在阻塞期間接收到的客戶端發送的請求  
  24.         if (c->querybuf && sdslen(c->querybuf) > 0) {  
  25.             server.current_client = c;  
  26.             processInputBuffer(c);  
  27.             server.current_client = NULL;  
  28.         }  
  29.     }  
  30.   
  31.     /* Write the AOF buffer on disk */  
  32.     //將server.aof_buf中的數據追加到AOF文件中並fsync到硬盤上  
  33.     flushAppendOnlyFile(0);  
  34. }  

通過上面的代碼及注釋可以發現,beforeSleep函數做了三件事:1、處理過期鍵,2、處理阻塞期間的客戶端請求,3、將server.aof_buf中的數據追加到AOF文件中並fsync刷新到硬盤上,flushAppendOnlyFile函數給定了一個參數force,表示是否強制寫入AOF文件,0表示非強制即支持延遲寫,1表示強制寫入。

 

 

[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. void flushAppendOnlyFile(int force) {  
  2.     ssize_t nwritten;  
  3.     int sync_in_progress = 0;  
  4.     if (sdslen(server.aof_buf) == 0) return;  
  5.     // 返回后台正在等待執行的 fsync 數量  
  6.     if (server.aof_fsync == AOF_FSYNC_EVERYSEC)  
  7.         sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;  
  8.   
  9.     // AOF 模式為每秒 fsync ,並且 force 不為 1 如果可以的話,推延沖洗  
  10.     if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {  
  11.         /* With this append fsync policy we do background fsyncing. 
  12.          * If the fsync is still in progress we can try to delay 
  13.          * the write for a couple of seconds. */  
  14.         // 如果 aof_fsync 隊列里已經有正在等待的任務  
  15.         if (sync_in_progress) {  
  16.             // 上一次沒有推遲沖洗過,記錄推延的當前時間,然后返回  
  17.             if (server.aof_flush_postponed_start == 0) {  
  18.                 /* No previous write postponinig, remember that we are 
  19.                  * postponing the flush and return. */  
  20.                 server.aof_flush_postponed_start = server.unixtime;  
  21.                 return;  
  22.             } else if (server.unixtime - server.aof_flush_postponed_start < 2) {  
  23.                 // 允許在兩秒之內的推延沖洗  
  24.                 /* We were already waiting for fsync to finish, but for less 
  25.                  * than two seconds this is still ok. Postpone again. */  
  26.                 return;  
  27.             }  
  28.             /* Otherwise fall trough, and go write since we can't wait 
  29.              * over two seconds. */  
  30.             server.aof_delayed_fsync++;  
  31.             redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");  
  32.         }  
  33.     }  
  34.     /* If you are following this code path, then we are going to write so 
  35.      * set reset the postponed flush sentinel to zero. */  
  36.     server.aof_flush_postponed_start = 0;  
  37.   
  38.     /* We want to perform a single write. This should be guaranteed atomic 
  39.      * at least if the filesystem we are writing is a real physical one. 
  40.      * While this will save us against the server being killed I don't think 
  41.      * there is much to do about the whole server stopping for power problems 
  42.      * or alike */  
  43.     // 將 AOF 緩存寫入到文件,如果一切幸運的話,寫入會原子性地完成  
  44.     nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));  
  45.     if (nwritten != (signed)sdslen(server.aof_buf)) {//出錯  
  46.         /* Ooops, we are in troubles. The best thing to do for now is 
  47.          * aborting instead of giving the illusion that everything is 
  48.          * working as expected. */  
  49.         if (nwritten == -1) {  
  50.             redisLog(REDIS_WARNING,"Exiting on error writing to the append-only file: %s",strerror(errno));  
  51.         } else {  
  52.             redisLog(REDIS_WARNING,"Exiting on short write while writing to "  
  53.                                    "the append-only file: %s (nwritten=%ld, "  
  54.                                    "expected=%ld)",  
  55.                                    strerror(errno),  
  56.                                    (long)nwritten,  
  57.                                    (long)sdslen(server.aof_buf));  
  58.   
  59.             if (ftruncate(server.aof_fd, server.aof_current_size) == -1) {  
  60.                 redisLog(REDIS_WARNING, "Could not remove short write "  
  61.                          "from the append-only file.  Redis may refuse "  
  62.                          "to load the AOF the next time it starts.  "  
  63.                          "ftruncate: %s", strerror(errno));  
  64.             }  
  65.         }  
  66.         exit(1);  
  67.     }  
  68.     server.aof_current_size += nwritten;  
  69.   
  70.     /* Re-use AOF buffer when it is small enough. The maximum comes from the 
  71.      * arena size of 4k minus some overhead (but is otherwise arbitrary). */  
  72.     // 如果 aof 緩存不是太大,那么重用它,否則,清空 aof 緩存  
  73.     if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {  
  74.         sdsclear(server.aof_buf);  
  75.     } else {  
  76.         sdsfree(server.aof_buf);  
  77.         server.aof_buf = sdsempty();  
  78.     }  
  79.   
  80.     /* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are 
  81.      * children doing I/O in the background. */  
  82.     //aof rdb子進程運行中不支持fsync並且aof rdb子進程正在運行,那么直接返回,  
  83.     //但是數據已經寫到aof文件中,只是沒有刷新到硬盤  
  84.     if (server.aof_no_fsync_on_rewrite &&  
  85.         (server.aof_child_pid != -1 || server.rdb_child_pid != -1))  
  86.             return;  
  87.   
  88.     /* Perform the fsync if needed. */  
  89.     if (server.aof_fsync == AOF_FSYNC_ALWAYS) {//總是fsync,那么直接進行fsync  
  90.         /* aof_fsync is defined as fdatasync() for Linux in order to avoid 
  91.          * flushing metadata. */  
  92.         aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */  
  93.         server.aof_last_fsync = server.unixtime;  
  94.     } else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&  
  95.                 server.unixtime > server.aof_last_fsync)) {  
  96.         if (!sync_in_progress) aof_background_fsync(server.aof_fd);//放到后台線程進行fsync  
  97.         server.aof_last_fsync = server.unixtime;  
  98.     }  
  99. }  

上述代碼中請關注server.aof_fsync參數,即設置Redis fsync AOF文件到硬盤的策略,如果設置為AOF_FSYNC_ALWAYS,那么直接在主進程中fsync,如果設置為AOF_FSYNC_EVERYSEC,那么放入后台線程中fsync,后台線程的代碼在bio.c中。

 

 

小結

文章寫到這,已經解決的了Redis Server啟動加載AOF文件和如何將客戶端請求產生的新的數據追加到AOF文件中,對於追加數據到AOF文件中,根據fsync的配置策略如何將寫入到AOF文件中的新數據刷新到硬盤中,直接在主進程中fsync或是在后台線程fsync。

至此,AOF數據持久化還剩下如何rewrite AOF,接受客戶端發送的BGREWRITEAOF請求,此部分內容待下篇博客中解析。

感謝此篇博客給我在理解Redis AOF數據持久化方面的巨大幫助,http://chenzhenianqing.cn/articles/786.html

本人Redis-2.8.2的源碼注釋已經放到Github中,有需要的讀者可以下載,我也會在后續的時間中更新,https://github.com/xkeyideal/annotated-redis-2.8.2

本人不怎么會使用Git,望有人能教我一下。

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------

本文所引用的源碼全部來自Redis2.8.2版本。

Redis AOF數據持久化機制的實現相關代碼是redis.c, redis.h, aof.c, bio.c, rio.c, config.c

在閱讀本文之前請先閱讀Redis數據持久化機制AOF原理分析之配置詳解文章,了解AOF相關參數的解析,文章鏈接

http://blog.csdn.net/acceptedxukai/article/details/18135219

接着上一篇文章,本文將介紹Redis是如何實現AOF rewrite的。

轉載請注明,文章出自http://blog.csdn.net/acceptedxukai/article/details/18181563

 

AOF rewrite的觸發機制

 

如果Redis只是將客戶端修改數據庫的指令重現存儲在AOF文件中,那么AOF文件的大小會不斷的增加,因為AOF文件只是簡單的重現存儲了客戶端的指令,而並沒有進行合並。對於該問題最簡單的處理方式,即當AOF文件滿足一定條件時就對AOF進行rewrite,rewrite是根據當前內存數據庫中的數據進行遍歷寫到一個臨時的AOF文件,待寫完后替換掉原來的AOF文件即可。

 

Redis觸發AOF rewrite機制有三種:

1、Redis Server接收到客戶端發送的BGREWRITEAOF指令請求,如果當前AOF/RDB數據持久化沒有在執行,那么執行,反之,等當前AOF/RDB數據持久化結束后執行AOF rewrite

2、在Redis配置文件redis.conf中,用戶設置了auto-aof-rewrite-percentage和auto-aof-rewrite-min-size參數,並且當前AOF文件大小server.aof_current_size大於auto-aof-rewrite-min-size(server.aof_rewrite_min_size),同時AOF文件大小的增長率大於auto-aof-rewrite-percentage(server.aof_rewrite_perc)時,會自動觸發AOF rewrite

3、用戶設置“config set appendonly yes”開啟AOF的時,調用startAppendOnly函數會觸發rewrite

下面分別介紹上述三種機制的處理.

 

接收到BGREWRITEAOF指令

 
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. <span style="font-size:12px;">void bgrewriteaofCommand(redisClient *c) {  
  2.     //AOF rewrite正在執行,那么直接返回  
  3.     if (server.aof_child_pid != -1) {  
  4.         addReplyError(c,"Background append only file rewriting already in progress");  
  5.     } else if (server.rdb_child_pid != -1) {  
  6.         //AOF rewrite未執行,但RDB數據持久化正在執行,那么設置AOF rewrite狀態為scheduled  
  7.         //待RDB結束后執行AOF rewrite  
  8.         server.aof_rewrite_scheduled = 1;  
  9.         addReplyStatus(c,"Background append only file rewriting scheduled");  
  10.     } else if (rewriteAppendOnlyFileBackground() == REDIS_OK) {  
  11.         //直接執行AOF rewrite  
  12.         addReplyStatus(c,"Background append only file rewriting started");  
  13.     } else {  
  14.         addReply(c,shared.err);  
  15.     }  
  16. }</span>  
當AOF rewrite請求被掛起時,在serverCron函數中,會處理。
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Start a scheduled AOF rewrite if this was requested by the user while 
  2.      * a BGSAVE was in progress. */  
  3.     // 如果用戶執行 BGREWRITEAOF 命令的話,在后台開始 AOF 重寫  
  4.     //當用戶執行BGREWRITEAOF命令時,如果RDB文件正在寫,那么將server.aof_rewrite_scheduled標記為1  
  5.     //當RDB文件寫完后開啟AOF rewrite  
  6.     if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&  
  7.         server.aof_rewrite_scheduled)  
  8.     {  
  9.         rewriteAppendOnlyFileBackground();  
  10.     }  


Server自動對AOF進行rewrite

在serverCron函數中會周期性判斷
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Trigger an AOF rewrite if needed */  
  2.          //滿足一定條件rewrite AOF文件  
  3.          if (server.rdb_child_pid == -1 &&  
  4.              server.aof_child_pid == -1 &&  
  5.              server.aof_rewrite_perc &&  
  6.              server.aof_current_size > server.aof_rewrite_min_size)  
  7.          {  
  8.             long long base = server.aof_rewrite_base_size ?  
  9.                             server.aof_rewrite_base_size : 1;  
  10.             long long growth = (server.aof_current_size*100/base) - 100;  
  11.             if (growth >= server.aof_rewrite_perc) {  
  12.                 redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);  
  13.                 rewriteAppendOnlyFileBackground();  
  14.             }  
  15.          }  

config set appendonly yes

當客戶端發送該指令時,config.c中的configSetCommand函數會做出響應,startAppendOnly函數會執行AOF rewrite
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. if (!strcasecmp(c->argv[2]->ptr,"appendonly")) {  
  2.     int enable = yesnotoi(o->ptr);  
  3.   
  4.     if (enable == -1) goto badfmt;  
  5.     if (enable == 0 && server.aof_state != REDIS_AOF_OFF) {//appendonly no 關閉AOF  
  6.         stopAppendOnly();  
  7.     } else if (enable && server.aof_state == REDIS_AOF_OFF) {//appendonly yes rewrite AOF  
  8.         if (startAppendOnly() == REDIS_ERR) {  
  9.             addReplyError(c,  
  10.                 "Unable to turn on AOF. Check server logs.");  
  11.             return;  
  12.         }  
  13.     }  
  14. }  
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. int startAppendOnly(void) {  
  2.     server.aof_last_fsync = server.unixtime;  
  3.     server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);  
  4.     redisAssert(server.aof_state == REDIS_AOF_OFF);  
  5.     if (server.aof_fd == -1) {  
  6.         redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't open the append only file: %s",strerror(errno));  
  7.         return REDIS_ERR;  
  8.     }  
  9.     if (rewriteAppendOnlyFileBackground() == REDIS_ERR) {//rewrite  
  10.         close(server.aof_fd);  
  11.         redisLog(REDIS_WARNING,"Redis needs to enable the AOF but can't trigger a background AOF rewrite operation. Check the above logs for more info about the error.");  
  12.         return REDIS_ERR;  
  13.     }  
  14.     /* We correctly switched on AOF, now wait for the rerwite to be complete 
  15.      * in order to append data on disk. */  
  16.     server.aof_state = REDIS_AOF_WAIT_REWRITE;  
  17.     return REDIS_OK;  
  18. }  

Redis AOF rewrite機制的實現

從上述分析可以看出rewrite的實現全部依靠rewriteAppendOnlyFileBackground函數,下面分析該函數,通過下面的代碼可以看出,Redis是fork出一個子進程來操作AOF rewrite,然后子進程調用rewriteAppendOnlyFile函數,將數據寫到一個臨時文件temp-rewriteaof-bg-%d.aof中。如果子進程完成會通過exit(0)函數通知父進程rewrite結束,在serverCron函數中使用wait3函數接收子進程退出狀態,然后執行后續的AOF rewrite的收尾工作,后面將會分析。
父進程的工作主要包括清楚server.aof_rewrite_scheduled標志,記錄子進程IDserver.aof_child_pid = childpid,記錄rewrite的開始時間server.aof_rewrite_time_start = time(NULL)等。
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. int rewriteAppendOnlyFileBackground(void) {  
  2.     pid_t childpid;  
  3.     long long start;  
  4.   
  5.     // 后台重寫正在執行  
  6.     if (server.aof_child_pid != -1) return REDIS_ERR;  
  7.     start = ustime();  
  8.     if ((childpid = fork()) == 0) {  
  9.         char tmpfile[256];  
  10.   
  11.         /* Child */  
  12.         closeListeningSockets(0);//  
  13.         redisSetProcTitle("redis-aof-rewrite");  
  14.         snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());  
  15.         if (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {  
  16.             size_t private_dirty = zmalloc_get_private_dirty();  
  17.   
  18.             if (private_dirty) {  
  19.                 redisLog(REDIS_NOTICE,  
  20.                     "AOF rewrite: %zu MB of memory used by copy-on-write",  
  21.                     private_dirty/(1024*1024));  
  22.             }  
  23.             exitFromChild(0);  
  24.         } else {  
  25.             exitFromChild(1);  
  26.         }  
  27.     } else {  
  28.         /* Parent */  
  29.         server.stat_fork_time = ustime()-start;  
  30.         if (childpid == -1) {  
  31.             redisLog(REDIS_WARNING,  
  32.                 "Can't rewrite append only file in background: fork: %s",  
  33.                 strerror(errno));  
  34.             return REDIS_ERR;  
  35.         }  
  36.         redisLog(REDIS_NOTICE,  
  37.             "Background append only file rewriting started by pid %d",childpid);  
  38.         server.aof_rewrite_scheduled = 0;  
  39.         server.aof_rewrite_time_start = time(NULL);  
  40.         server.aof_child_pid = childpid;  
  41.         updateDictResizePolicy();  
  42.         /* We set appendseldb to -1 in order to force the next call to the 
  43.          * feedAppendOnlyFile() to issue a SELECT command, so the differences 
  44.          * accumulated by the parent into server.aof_rewrite_buf will start 
  45.          * with a SELECT statement and it will be safe to merge. */  
  46.         server.aof_selected_db = -1;  
  47.         replicationScriptCacheFlush();  
  48.         return REDIS_OK;  
  49.     }  
  50.     return REDIS_OK; /* unreached */  
  51. }  
接下來介紹rewriteAppendOnlyFile函數,該函數的主要工作為:遍歷所有數據庫中的數據,將其寫入到臨時文件temp-rewriteaof-%d.aof中,寫入函數定義在rio.c中,比較簡單,然后將數據刷新到硬盤中,然后將文件名rename為其調用者給定的臨時文件名,注意仔細看代碼,這里並沒有修改為正式的AOF文件名。
在寫入文件時如果設置server.aof_rewrite_incremental_fsync參數,那么在rioWrite函數中fwrite部分數據就會將數據fsync到硬盤中,來保證數據的正確性。
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. int rewriteAppendOnlyFile(char *filename) {  
  2.     dictIterator *di = NULL;  
  3.     dictEntry *de;  
  4.     rio aof;  
  5.     FILE *fp;  
  6.     char tmpfile[256];  
  7.     int j;  
  8.     long long now = mstime();  
  9.   
  10.     /* Note that we have to use a different temp name here compared to the 
  11.      * one used by rewriteAppendOnlyFileBackground() function. */  
  12.     snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());  
  13.     fp = fopen(tmpfile,"w");  
  14.     if (!fp) {  
  15.         redisLog(REDIS_WARNING, "Opening the temp file for AOF rewrite in rewriteAppendOnlyFile(): %s", strerror(errno));  
  16.         return REDIS_ERR;  
  17.     }  
  18.   
  19.     rioInitWithFile(&aof,fp); //初始化讀寫函數,rio.c  
  20.     //設置r->io.file.autosync = bytes;每32M刷新一次  
  21.     if (server.aof_rewrite_incremental_fsync)  
  22.         rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);  
  23.     for (j = 0; j < server.dbnum; j++) {//遍歷每個數據庫  
  24.         char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";  
  25.         redisDb *db = server.db+j;  
  26.         dict *d = db->dict;  
  27.         if (dictSize(d) == 0) continue;  
  28.         di = dictGetSafeIterator(d);  
  29.         if (!di) {  
  30.             fclose(fp);  
  31.             return REDIS_ERR;  
  32.         }  
  33.   
  34.         /* SELECT the new DB */  
  35.         if (rioWrite(&aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;  
  36.         if (rioWriteBulkLongLong(&aof,j) == 0) goto werr;  
  37.   
  38.         /* Iterate this DB writing every entry */  
  39.         while((de = dictNext(di)) != NULL) {  
  40.             sds keystr;  
  41.             robj key, *o;  
  42.             long long expiretime;  
  43.   
  44.             keystr = dictGetKey(de);  
  45.             o = dictGetVal(de);  
  46.             initStaticStringObject(key,keystr);  
  47.   
  48.             expiretime = getExpire(db,&key);  
  49.   
  50.             /* If this key is already expired skip it */  
  51.             if (expiretime != -1 && expiretime < now) continue;  
  52.   
  53.             /* Save the key and associated value */  
  54.             if (o->type == REDIS_STRING) {  
  55.                 /* Emit a SET command */  
  56.                 char cmd[]="*3\r\n$3\r\nSET\r\n";  
  57.                 if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;  
  58.                 /* Key and value */  
  59.                 if (rioWriteBulkObject(&aof,&key) == 0) goto werr;  
  60.                 if (rioWriteBulkObject(&aof,o) == 0) goto werr;  
  61.             } else if (o->type == REDIS_LIST) {  
  62.                 if (rewriteListObject(&aof,&key,o) == 0) goto werr;  
  63.             } else if (o->type == REDIS_SET) {  
  64.                 if (rewriteSetObject(&aof,&key,o) == 0) goto werr;  
  65.             } else if (o->type == REDIS_ZSET) {  
  66.                 if (rewriteSortedSetObject(&aof,&key,o) == 0) goto werr;  
  67.             } else if (o->type == REDIS_HASH) {  
  68.                 if (rewriteHashObject(&aof,&key,o) == 0) goto werr;  
  69.             } else {  
  70.                 redisPanic("Unknown object type");  
  71.             }  
  72.             /* Save the expire time */  
  73.             if (expiretime != -1) {  
  74.                 char cmd[]="*3\r\n$9\r\nPEXPIREAT\r\n";  
  75.                 if (rioWrite(&aof,cmd,sizeof(cmd)-1) == 0) goto werr;  
  76.                 if (rioWriteBulkObject(&aof,&key) == 0) goto werr;  
  77.                 if (rioWriteBulkLongLong(&aof,expiretime) == 0) goto werr;  
  78.             }  
  79.         }  
  80.         dictReleaseIterator(di);  
  81.     }  
  82.   
  83.     /* Make sure data will not remain on the OS's output buffers */  
  84.     fflush(fp);  
  85.     aof_fsync(fileno(fp));//將tempfile文件刷新到硬盤  
  86.     fclose(fp);  
  87.   
  88.     /* Use RENAME to make sure the DB file is changed atomically only 
  89.      * if the generate DB file is ok. */  
  90.     if (rename(tmpfile,filename) == -1) {//重命名文件名,注意rename后的文件也是一個臨時文件  
  91.         redisLog(REDIS_WARNING,"Error moving temp append only file on the final destination: %s", strerror(errno));  
  92.         unlink(tmpfile);  
  93.         return REDIS_ERR;  
  94.     }  
  95.     redisLog(REDIS_NOTICE,"SYNC append only file rewrite performed");  
  96.     return REDIS_OK;  
  97.   
  98. werr:  
  99.     fclose(fp);  
  100.     unlink(tmpfile);  
  101.     redisLog(REDIS_WARNING,"Write error writing append only file on disk: %s", strerror(errno));  
  102.     if (di) dictReleaseIterator(di);  
  103.     return REDIS_ERR;  
  104. }  
AOF rewrite工作到這里已經結束一半,上一篇文章提到如果server.aof_state != REDIS_AOF_OFF,那么就會將客戶端請求指令修改的數據通過feedAppendOnlyFile函數追加到AOF文件中,那么此時AOF已經rewrite了,必須要處理此時出現的差異數據,記得在feedAppendOnlyFile函數中有這么一段代碼
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. if (server.aof_child_pid != -1)  
  2.         aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));  
如果AOF rewrite正在進行,那么就將修改數據的指令字符串存儲到server.aof_rewrite_buf_blocks鏈表中,等待AOF rewrite子進程結束后處理,處理此部分數據的代碼在serverCron函數中。需要指出的是wait3函數我不了解,可能下面注釋會有點問題。
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* Check if a background saving or AOF rewrite in progress terminated. */  
  2. //如果RDB bgsave或AOF rewrite子進程已經執行,通過獲取子進程的退出狀態,對后續的工作進行處理  
  3. if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {//  
  4.     int statloc;  
  5.     pid_t pid;  
  6.   
  7.     if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {  
  8.         int exitcode = WEXITSTATUS(statloc);//獲取退出的狀態  
  9.         int bysignal = 0;  
  10.   
  11.         if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);  
  12.   
  13.         if (pid == server.rdb_child_pid) {  
  14.             backgroundSaveDoneHandler(exitcode,bysignal);  
  15.         } else if (pid == server.aof_child_pid) {  
  16.             backgroundRewriteDoneHandler(exitcode,bysignal);  
  17.         } else {  
  18.             redisLog(REDIS_WARNING,  
  19.                 "Warning, detected child with unmatched pid: %ld",  
  20.                 (long)pid);  
  21.         }  
  22.         // 如果 BGSAVE 和 BGREWRITEAOF 都已經完成,那么重新開始 REHASH  
  23.         updateDictResizePolicy();  
  24.     }  
  25. }  
對於AOF rewrite期間出現的差異數據,Server通過backgroundSaveDoneHandler函數將 server.aof_rewrite_buf_blocks鏈表中數據追加到新的AOF文件中。
backgroundSaveDoneHandler函數執行步驟:
1、通過判斷子進程的退出狀態,正確的退出狀態為exit(0),即exitcode為0,bysignal我不清楚具體意義,如果退出狀態正確,backgroundSaveDoneHandler函數才會開始處理
2、通過對rewriteAppendOnlyFileBackground函數的分析,可以知道rewrite后的AOF臨時文件名為temp-rewriteaof-bg-%d.aof(%d=server.aof_child_pid)中,接着需要打開此臨時文件
3、調用aofRewriteBufferWrite函數將server.aof_rewrite_buf_blocks中差異數據寫到該臨時文件中
4、如果舊的AOF文件未打開,那么打開舊的AOF文件,將文件描述符賦值給臨時變量oldfd
5、將臨時的AOF文件名rename為正常的AOF文件名
6、如果舊的AOF文件未打開,那么此時只需要關閉新的AOF文件,此時的server.aof_rewrite_buf_blocks數據應該為空;如果舊的AOF是打開的,那么將server.aof_fd指向newfd,然后根據相應的fsync策略將數據刷新到硬盤上
7、調用aofUpdateCurrentSize函數統計AOF文件的大小,更新server.aof_rewrite_base_size,為serverCron中自動AOF rewrite做相應判斷
8、如果之前是REDIS_AOF_WAIT_REWRITE狀態,則設置server.aof_state為REDIS_AOF_ON,因為只有“config set appendonly yes”指令才會設置這個狀態,也就是需要寫完快照后,立即打開AOF;而BGREWRITEAOF不需要打開AOF
9、調用后台線程去關閉舊的AOF文件
下面是backgroundSaveDoneHandler函數的注釋代碼
 
[cpp]  view plain copy print ? 在CODE上查看代碼片 派生到我的代碼片
 
  1. /* A background append only file rewriting (BGREWRITEAOF) terminated its work. 
  2.  * Handle this. */  
  3. void backgroundRewriteDoneHandler(int exitcode, int bysignal) {  
  4.     if (!bysignal && exitcode == 0) {//子進程退出狀態正確  
  5.         int newfd, oldfd;  
  6.         char tmpfile[256];  
  7.         long long now = ustime();  
  8.   
  9.         redisLog(REDIS_NOTICE,  
  10.             "Background AOF rewrite terminated with success");  
  11.   
  12.         /* Flush the differences accumulated by the parent to the 
  13.          * rewritten AOF. */  
  14.         snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",  
  15.             (int)server.aof_child_pid);  
  16.         newfd = open(tmpfile,O_WRONLY|O_APPEND);  
  17.         if (newfd == -1) {  
  18.             redisLog(REDIS_WARNING,  
  19.                 "Unable to open the temporary AOF produced by the child: %s", strerror(errno));  
  20.             goto cleanup;  
  21.         }  
  22.         //處理server.aof_rewrite_buf_blocks中DIFF數據  
  23.         if (aofRewriteBufferWrite(newfd) == -1) {  
  24.             redisLog(REDIS_WARNING,  
  25.                 "Error trying to flush the parent diff to the rewritten AOF: %s", strerror(errno));  
  26.             close(newfd);  
  27.             goto cleanup;  
  28.         }  
  29.   
  30.         redisLog(REDIS_NOTICE,  
  31.             "Parent diff successfully flushed to the rewritten AOF (%lu bytes)", aofRewriteBufferSize());  
  32.   
  33.         /* The only remaining thing to do is to rename the temporary file to 
  34.          * the configured file and switch the file descriptor used to do AOF 
  35.          * writes. We don't want close(2) or rename(2) calls to block the 
  36.          * server on old file deletion. 
  37.          * 
  38.          * There are two possible scenarios: 
  39.          * 
  40.          * 1) AOF is DISABLED and this was a one time rewrite. The temporary 
  41.          * file will be renamed to the configured file. When this file already 
  42.          * exists, it will be unlinked, which may block the server. 
  43.          * 
  44.          * 2) AOF is ENABLED and the rewritten AOF will immediately start 
  45.          * receiving writes. After the temporary file is renamed to the 
  46.          * configured file, the original AOF file descriptor will be closed. 
  47.          * Since this will be the last reference to that file, closing it 
  48.          * causes the underlying file to be unlinked, which may block the 
  49.          * server. 
  50.          * 
  51.          * To mitigate the blocking effect of the unlink operation (either 
  52.          * caused by rename(2) in scenario 1, or by close(2) in scenario 2), we 
  53.          * use a background thread to take care of this. First, we 
  54.          * make scenario 1 identical to scenario 2 by opening the target file 
  55.          * when it exists. The unlink operation after the rename(2) will then 
  56.          * be executed upon calling close(2) for its descriptor. Everything to 
  57.          * guarantee atomicity for this switch has already happened by then, so 
  58.          * we don't care what the outcome or duration of that close operation 
  59.          * is, as long as the file descriptor is released again. */  
  60.         if (server.aof_fd == -1) {  
  61.             /* AOF disabled */  
  62.   
  63.              /* Don't care if this fails: oldfd will be -1 and we handle that. 
  64.               * One notable case of -1 return is if the old file does 
  65.               * not exist. */  
  66.              oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);  
  67.         } else {  
  68.             /* AOF enabled */  
  69.             oldfd = -1; /* We'll set this to the current AOF filedes later. */  
  70.         }  
  71.   
  72.         /* Rename the temporary file. This will not unlink the target file if 
  73.          * it exists, because we reference it with "oldfd". */  
  74.         //把臨時文件改名為正常的AOF文件名。由於當前oldfd已經指向這個之前的正常文件名的文件,  
  75.         //所以當前不會造成unlink操作,得等那個oldfd被close的時候,內核判斷該文件沒有指向了,就刪除之。  
  76.         if (rename(tmpfile,server.aof_filename) == -1) {  
  77.             redisLog(REDIS_WARNING,  
  78.                 "Error trying to rename the temporary AOF file: %s", strerror(errno));  
  79.             close(newfd);  
  80.             if (oldfd != -1) close(oldfd);  
  81.             goto cleanup;  
  82.         }  
  83.         //如果AOF關閉了,那只要處理新文件,直接關閉這個新的文件即可  
  84.         //但是這里會不會導致服務器卡呢?這個newfd應該是臨時文件的最后一個fd了,不會的,  
  85.         //因為這個文件在本函數不會寫入數據,因為stopAppendOnly函數會清空aof_rewrite_buf_blocks列表。  
  86.         if (server.aof_fd == -1) {  
  87.             /* AOF disabled, we don't need to set the AOF file descriptor 
  88.              * to this new file, so we can close it. */  
  89.             close(newfd);  
  90.         } else {  
  91.             /* AOF enabled, replace the old fd with the new one. */  
  92.             oldfd = server.aof_fd;  
  93.             //指向新的fd,此時這個fd由於上面的rename語句存在,已經為正常aof文件名  
  94.             server.aof_fd = newfd;  
  95.             //fsync到硬盤  
  96.             if (server.aof_fsync == AOF_FSYNC_ALWAYS)  
  97.                 aof_fsync(newfd);  
  98.             else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)  
  99.                 aof_background_fsync(newfd);  
  100.             server.aof_selected_db = -1; /* Make sure SELECT is re-issued */  
  101.             aofUpdateCurrentSize();  
  102.             server.aof_rewrite_base_size = server.aof_current_size;  
  103.   
  104.             /* Clear regular AOF buffer since its contents was just written to 
  105.              * the new AOF from the background rewrite buffer. */  
  106.             //rewrite得到的肯定是最新的數據,所以aof_buf中的數據沒有意義,直接清空  
  107.             sdsfree(server.aof_buf);  
  108.             server.aof_buf = sdsempty();  
  109.         }  
  110.   
  111.         server.aof_lastbgrewrite_status = REDIS_OK;  
  112.   
  113.         redisLog(REDIS_NOTICE, "Background AOF rewrite finished successfully");  
  114.         /* Change state from WAIT_REWRITE to ON if needed */  
  115.         //下面判斷是否需要打開AOF,比如bgrewriteaofCommand就不需要打開AOF。  
  116.         if (server.aof_state == REDIS_AOF_WAIT_REWRITE)  
  117.             server.aof_state = REDIS_AOF_ON;  
  118.   
  119.         /* Asynchronously close the overwritten AOF. */  
  120.         //讓后台線程去關閉這個舊的AOF文件FD,只要CLOSE就行,會自動unlink的,因為上面已經有rename  
  121.         if (oldfd != -1) bioCreateBackgroundJob(REDIS_BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL);  
  122.   
  123.         redisLog(REDIS_VERBOSE,  
  124.             "Background AOF rewrite signal handler took %lldus", ustime()-now);  
  125.     } else if (!bysignal && exitcode != 0) {  
  126.         server.aof_lastbgrewrite_status = REDIS_ERR;  
  127.   
  128.         redisLog(REDIS_WARNING,  
  129.             "Background AOF rewrite terminated with error");  
  130.     } else {  
  131.         server.aof_lastbgrewrite_status = REDIS_ERR;  
  132.   
  133.         redisLog(REDIS_WARNING,  
  134.             "Background AOF rewrite terminated by signal %d", bysignal);  
  135.     }  
  136.   
  137. cleanup:  
  138.     aofRewriteBufferReset();  
  139.     aofRemoveTempFile(server.aof_child_pid);  
  140.     server.aof_child_pid = -1;  
  141.     server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;  
  142.     server.aof_rewrite_time_start = -1;  
  143.     /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */  
  144.     if (server.aof_state == REDIS_AOF_WAIT_REWRITE)  
  145.         server.aof_rewrite_scheduled = 1;  
  146. }  
 
至此,AOF數據持久化已經全部結束了,剩下的就是一些細節的處理,以及一些Linux庫函數的理解,對於rename、unlink、wait3等庫 函數的深入認識就去問Google吧。
 

小結

 
Redis AOF數據持久化的實現機制通過三篇文章基本上比較詳細的分析了, 但這只是從代碼層面去看AOF,對於AOF持久化的優缺點網上有很多分析,Redis的官方網站也有英文介紹,Redis的數據持久化還有一種方法叫RDB,更多RDB的內容等下次再分析。
感謝此篇博客給我在理解Redis AOF數據持久化方面的巨大幫助, http://chenzhenianqing.cn/articles/786.html,此篇博客對AOF的分析十分的詳細。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM