zoukankan      html  css  js  c++  java
  • 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数据库中。

    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文件中的数据要新。

    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 跳过 */  
    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参数的初始化,下面解决这个疑问。

    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()函数中

    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文件中。具体见下面代码的注释。

    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. }  
    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 $6 SELECT $%lu %s ",  
    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函数,下面看看这个函数做了什么工作?

    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表示强制写入。

    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指令

     
    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函数中,会处理。
    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函数中会周期性判断
    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
    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. }  
    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)等。
    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到硬盘中,来保证数据的正确性。
    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 $6 SELECT ";  
    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 $3 SET ";  
    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 $9 PEXPIREAT ";  
    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函数中有这么一段代码
    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函数我不了解,可能下面注释会有点问题。
    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函数的注释代码
     
    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的分析十分的详细。
  • 相关阅读:
    Spring 学习笔记
    Hello Spring(3)ConnectionUtility
    android ndk开发
    java udp 广播及socket通讯
    android 横向滚动屏幕实现(2)
    android 基于ftp远程文件管理
    android 软键盘的弹出问题总结
    android 滚动字幕
    android 基于apache ftp server
    android 设置壁纸几种方法
  • 原文地址:https://www.cnblogs.com/davidwang456/p/3521182.html
Copyright © 2011-2022 走看看