Redis数据持久化机制AOF原理分析一---转

发布时间:2019-02-25 整理:脚本之家
脚本之家收集整理的这篇文章主要介绍了Redis数据持久化机制AOF原理分析一---转脚本之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随脚本之家小编过来看看吧!

本文所引用的源码全部来自Redis2.8.2版本。

Redis AOF数据持久化机制的实现相关代码是redis.c,redis.h,aof.c,bio.c,rio.c,config.c

在阅读本文之前请先阅读Redis数据持久化机制AOF原理分析之配置详解文章,了解AOF相关参数的解析,文章链接

转载请注明,文章出自

下面将介绍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数据库中。

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="
http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
      
  1.  loadDataFromDisk() {  
  2.       start = ustime();  
  3.      (server.aof_state == REDIS_AOF_ON) {  
  4.          (loadAppendOnlyFile(server.aof_filename) == REDIS_OK)  
  5.             redisLog(REDIS_NOTICE,,()(ustime()-start)/1000000);  
  6.     }  {  
  7.          (rdbLoad(server.rdb_filename) == REDIS_OK) {  
  8.             redisLog(REDIS_NOTICE,,  
  9.                 ()(ustime()-start)/1000000);  
  10.         }   (errno != ENOENT) {  
  11.             redisLog(REDIS_WARNING,,strerror(errno));  
  12.             exit(1);  
  13.         }  
  14.     }  
  15. }  

Server首先判断加载AOF文件是因为AOF文件中的数据要比RDB文件中的数据要新。

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
     loadAppendOnlyFile( *filename) {  
  1.      redisClient *fakeClient;  
  2.      *fp = fopen(filename,);  
  3.      redis_stat sb;  
  4.      old_aof_state = server.aof_state;  
  5.      loops = 0;  
  6.   
  7.       
  8.       
  9.      (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {  
  10.         server.aof_current_size = 0;  
  11.         fclose(fp);  
  12.          REDIS_ERR;  
  13.     }  
  14.   
  15.      (fp == NULL) {  
  16.         redisLog(REDIS_WARNING,,strerror(errno));  
  17.         exit(1);  
  18.     }  
  19.   
  20.      
  21.   
  22.     server.aof_state = REDIS_AOF_OFF;  
  23.   
  24.     fakeClient = createFakeClient();   
  25.     startLoading(fp);   
  26.   
  27.     (1) {  
  28.          argc, j;  
  29.         unsigned  len;  
  30.         robj **argv;  
  31.          buf[128];  
  32.         sds argsds;  
  33.          redisCommand *cmd;  
  34.   
  35.           
  36.           
  37.          (!(loops++ % 1000)) {  
  38.             loadingProgress(ftello(fp));  
  39.             aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);  
  40.         }  
  41.           
  42.          (fgets(buf,(buf),fp) == NULL) {  
  43.              (feof(fp))  
  44.                 ;  
  45.               
  46.                  readerr;  
  47.         }  
  48.           
  49.          (buf[0] !=  fmterr;  
  50.         argc = atoi(buf+1);  
  51.          (argc < 1)  fmterr;  
  52.   
  53.         argv = zmalloc((robj*)*argc);  
  54.          (j = 0; j < argc; j++) {  
  55.              (fgets(buf,fp) == NULL)  readerr;  
  56.              (buf[0] !=  fmterr;  
  57.             len = strtol(buf+1,NULL,10);  
  58.             argsds = sdsnewlen(NULL,len);  
  59.               
  60.              (len && fread(argsds,len,1,fp) == 0)  fmterr;  
  61.             argv[j] = createObject(REDIS_STRING,argsds);  
  62.              (fread(buf,2,fp) == 0)  fmterr;   
  63.         }  
  64.   
  65.           
  66.         cmd = lookupCommand(argv[0]->ptr);  
  67.          (!cmd) {  
  68.             redisLog(REDIS_WARNING,, (*)argv[0]->ptr);  
  69.             exit(1);  
  70.         }  
  71.           
  72.         fakeClient->argc = argc;  
  73.         fakeClient->argv = argv;  
  74.         cmd->proc(fakeClient);  
  75.   
  76.           
  77.         redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);  
  78.           
  79.         redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);  
  80.   
  81.          
  82.   
  83.          (j = 0; j < fakeClient->argc; j++)  
  84.             decrRefCount(fakeClient->argv[j]);  
  85.         zfree(fakeClient->argv);  
  86.     }  
  87.   
  88.      
  89.   
  90.      (fakeClient->flags & REDIS_MULTI)  readerr;  
  91.   
  92.     fclose(fp);  
  93.     freeFakeClient(fakeClient);  
  94.     server.aof_state = old_aof_state;  
  95.     stopLoading();  
  96.     aofUpdateCurrentSize();   
  97.     server.aof_rewrite_base_size = server.aof_current_size;  
  98.      REDIS_OK;  
  99.     …………  
  100. }  

在前面一篇关于AOF参数配置的博客遗留了一个问题,server.aof_current_size参数的初始化,下面解决这个疑问。

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
     aofUpdateCurrentSize() {  
  1.      redis_stat sb;  
  2.   
  3.      (redis_fstat(server.aof_fd,&sb) == -1) {  
  4.         redisLog(REDIS_WARNING,,  
  5.             strerror(errno));  
  6.     }  {  
  7.         server.aof_current_size = sb.st_size;  
  8.     }  
  9. }  

redis_fstat是作者对Linux中fstat64函数的重命名,该还是就是获取文件相关的参数信息,具体可以Google之,sb.st_size就是当前AOF文件的大小。这里需要知道server.aof_fd即AOF文件描述符,该参数的初始化在initServer()函数中

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
      
  1.      (server.aof_state == REDIS_AOF_ON) {  
  2.         server.aof_fd = open(server.aof_filename,O_WRONLY|O_APPEND|O_CREAT,0644);  
  3.          (server.aof_fd == -1) {  
  4.             redisLog(REDIS_WARNING, ,strerror(errno));  
  5.             exit(1);  
  6.         }  
  7.     }  

 

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

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="
http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
     
  1.  
  2.  
  3.  
  4.  
  5.  
  6.  
  7.   
  8.  propagate( redisCommand *cmd,  dbid, robj **argv,  argc,  
  9.                 flags)  
  10. {  
  11.       
  12.      (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF)  
  13.         feedAppendOnlyFile(cmd,dbid,argv,argc);  
  14.      (flags & REDIS_PROPAGATE_REPL)  
  15.         replicationFeedSlaves(server.slaves,argc);  
  16. }  
[cpp] 在CODE上查看代码片

派生到我的代码片

 
      
  1.  feedAppendOnlyFile( redisCommand *cmd,  dictid,  argc) {  
  2.     sds buf = sdsempty();  
  3.     robj *tmpargv[3];  
  4.   
  5.      
  6.   
  7.       
  8.      (dictid != server.aof_selected_db) {  
  9.          seldb[64];  
  10.   
  11.         snprintf(seldb,(seldb),,dictid);  
  12.         buf = sdscatprintf(buf,,  
  13.             (unsigned )strlen(seldb),seldb);  
  14.         server.aof_selected_db = dictid;  
  15.     }  
  16.   
  17.       
  18.      (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||  
  19.         cmd->proc == expireatCommand) {  
  20.           
  21.         buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);  
  22.     }  
  23.       (cmd->proc == setexCommand || cmd->proc == psetexCommand) {  
  24.           
  25.         tmpargv[0] = createStringObject(,3);  
  26.         tmpargv[1] = argv[1];  
  27.         tmpargv[2] = argv[3];  
  28.         buf = catAppendOnlyGenericCommand(buf,3,tmpargv);  
  29.         decrRefCount(tmpargv[0]);  
  30.         buf = catAppendOnlyExpireAtCommand(buf,argv[2]);  
  31.     }  {  
  32.          
  33.  
  34.   
  35.         buf = catAppendOnlyGenericCommand(buf,argc,argv);  
  36.     }  
  37.   
  38.      
  39.  
  40.   
  41.       
  42.      (server.aof_state == REDIS_AOF_ON)  
  43.         server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));  
  44.   
  45.      
  46.  
  47.  
  48.   
  49.       
  50.       
  51.       
  52.       
  53.       
  54.      (server.aof_child_pid != -1)  
  55.         aofRewriteBufferAppend((unsigned *)buf,sdslen(buf));  
  56.      
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.   
  65.   
  66.     sdsfree(buf);  
  67. }  

 

Server在每次事件循环之前会调用一次beforeSleep函数,下面看看这个函数做了什么工作?

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="
http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
     
  1.  
  2.   
  3.  beforeSleep( aeEventLoop *eventLoop) {  
  4.     REDIS_NOTUSED(eventLoop);  
  5.     listNode *ln;  
  6.     redisClient *c;  
  7.   
  8.      
  9.   
  10.      (server.active_expire_enabled && server.masterhost == NULL)  
  11.         activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);  
  12.   
  13.       
  14.      (listLength(server.unblocked_clients)) {  
  15.         ln = listFirst(server.unblocked_clients);  
  16.         redisAssert(ln != NULL);  
  17.         c = ln->value;  
  18.         listDelNode(server.unblocked_clients,ln);  
  19.         c->flags &= ~REDIS_UNBLOCKED;  
  20.   
  21.           
  22.           
  23.          (c->querybuf && sdslen(c->querybuf) > 0) {  
  24.             server.current_client = c;  
  25.             processInputBuffer(c);  
  26.             server.current_client = NULL;  
  27.         }  
  28.     }  
  29.   
  30.       
  31.       
  32.     flushAppendOnlyFile(0);  
  33. }  

通过上面的代码及注释可以发现,beforeSleep函数做了三件事:1、处理过期键,2、处理阻塞期间的客户端请求,3、将server.aof_buf中的数据追加到AOF文件中并fsync刷新到硬盘上,flushAppendOnlyFile函数给定了一个参数force,表示是否强制写入AOF文件,0表示非强制即支持延迟写,1表示强制写入。

<div class="dp-highlighter bg_cpp">
<div class="bar">
<div class="tools">
[cpp] <a class="ViewSource" title="view plain" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;view plain<a class="CopyToClipboard" title="copy" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;copy<a class="PrintSource" title="print" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;print<a class="About" title="?" href="http://blog.csdn.net/acceptedxukai/article/details/18136903"&gt;?<a title="在CODE上查看代码片" href="https://code.csdn.net/snippets/152074" target="_blank">

<img src="https://code.csdn.net/assets/CODE_ico.png" alt="在CODE上查看代码片" width="12" height="12">

<a title="派生到我的代码片" href="https://code.csdn.net/snippets/152074/fork" target="_blank">

<img src="https://code.csdn.net/assets/ico_fork.svg" alt="派生到我的代码片" width="12" height="12">

 
     flushAppendOnlyFile( force) {  
  1.     ssize_t nwritten;  
  2.      sync_in_progress = 0;  
  3.      (sdslen(server.aof_buf) == 0) ;  
  4.       
  5.      (server.aof_fsync == AOF_FSYNC_EVERYSEC)  
  6.         sync_in_progress = bioPendingJobsOfType(REDIS_BIO_AOF_FSYNC) != 0;  
  7.   
  8.       
  9.      (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {  
  10.          
  11.  
  12.   
  13.           
  14.          (sync_in_progress) {  
  15.               
  16.              (server.aof_flush_postponed_start == 0) {  
  17.                  
  18.   
  19.                 server.aof_flush_postponed_start = server.unixtime;  
  20.                 ;  
  21.             }   (server.unixtime - server.aof_flush_postponed_start < 2) {  
  22.                   
  23.                  
  24.   
  25.                 ;  
  26.             }  
  27.              
  28.   
  29.             server.aof_delayed_fsync++;  
  30.             redisLog(REDIS_NOTICE,);  
  31.         }  
  32.     }  
  33.      
  34.   
  35.     server.aof_flush_postponed_start = 0;  
  36.   
  37.      
  38.  
  39.  
  40.  
  41.   
  42.       
  43.     nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));  
  44.      (nwritten != ()sdslen(server.aof_buf)) {  
  45.          
  46.  
  47.   
  48.          (nwritten == -1) {  
  49.             redisLog(REDIS_WARNING,,strerror(errno));  
  50.         }  {  
  51.             redisLog(REDIS_WARNING,  
  52.                                      
  53.                                    ,  
  54.                                    strerror(errno),  
  55.                                    ()nwritten,  
  56.                                    ()sdslen(server.aof_buf));  
  57.   
  58.              (ftruncate(server.aof_fd, server.aof_current_size) == -1) {  
  59.                 redisLog(REDIS_WARNING,   
  60.                            
  61.                            
  62.                          , strerror(errno));  
  63.             }  
  64.         }  
  65.         exit(1);  
  66.     }  
  67.     server.aof_current_size += nwritten;  
  68.   
  69.      
  70.   
  71.       
  72.      ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {  
  73.         sdsclear(server.aof_buf);  
  74.     }  {  
  75.         sdsfree(server.aof_buf);  
  76.         server.aof_buf = sdsempty();  
  77.     }  
  78.   
  79.      
  80.   
  81.       
  82.       
  83.      (server.aof_no_fsync_on_rewrite &&  
  84.         (server.aof_child_pid != -1 || server.rdb_child_pid != -1))  
  85.             ;  
  86.   
  87.       
  88.      (server.aof_fsync == AOF_FSYNC_ALWAYS) {  
  89.          
  90.   
  91.         aof_fsync(server.aof_fd);   
  92.         server.aof_last_fsync = server.unixtime;  
  93.     }   ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&  
  94.                 server.unixtime > server.aof_last_fsync)) {  
  95.          (!sync_in_progress) aof_background_fsync(server.aof_fd);  
  96.         server.aof_last_fsync = server.unixtime;  
  97.     }  
  98. }  

上述代码中请关注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数据持久化方面的巨大帮助,

本人Redis-2.8.2的源码注释已经放到Github中,有需要的读者可以下载,我也会在后续的时间中更新,

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

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

本文所引用的源码全部来自Redis2.8.2版本。

Redis AOF数据持久化机制的实现相关代码是redis.c,config.c

在阅读本文之前请先阅读Redis数据持久化机制AOF原理分析之配置详解文章,了解AOF相关参数的解析,文章链接

接着上一篇文章,本文将介绍Redis是如何实现AOF rewrite的。

转载请注明,文章出自

 

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] 在CODE上查看代码片

派生到我的代码片

 
    > bgrewriteaofCommand(redisClient *c) {  
  1.       
  2.      (server.aof_child_pid != -1) {  
  3.         addReplyError(c,);  
  4.     }   (server.rdb_child_pid != -1) {  
  5.           
  6.           
  7.         server.aof_rewrite_scheduled = 1;  
  8.         addReplyStatus(c,);  
  9.     }   (rewriteAppendOnlyFileBackground() == REDIS_OK) {  
  10.           
  11.         addReplyStatus(c,);  
  12.     }  {  
  13.         addReply(c,shared.err);  
  14.     }  
  15. }  
当AOF rewrite请求被挂起时,在serverCron函数中,会处理。
[cpp] 在CODE上查看代码片

派生到我的代码片

 
     
  1.   
  2.       
  3.       
  4.       
  5.      (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&  
  6.         server.aof_rewrite_scheduled)  
  7.     {  
  8.         rewriteAppendOnlyFileBackground();  
  9.     }  

Server自动对AOF进行rewrite

在serverCron函数中会周期性判断
[cpp] 在CODE上查看代码片

派生到我的代码片

 
      
  1.            
  2.           (server.rdb_child_pid == -1 &&  
  3.              server.aof_child_pid == -1 &&  
  4.              server.aof_rewrite_perc &&  
  5.              server.aof_current_size > server.aof_rewrite_min_size)  
  6.          {  
  7.               base = server.aof_rewrite_base_size ?  
  8.                             server.aof_rewrite_base_size : 1;  
  9.               growth = (server.aof_current_size*100/base) - 100;  
  10.              (growth >= server.aof_rewrite_perc) {  
  11.                 redisLog(REDIS_NOTICE,,growth);  
  12.                 rewriteAppendOnlyFileBackground();  
  13.             }  
  14.          }  

config set appendonly yes

当客户端发送该指令时,config.c中的configSetCommand函数会做出响应,startAppendOnly函数会执行AOF rewrite
[cpp] 在CODE上查看代码片

派生到我的代码片

 
     (!strcasecmp(c->argv[2]->ptr,)) {  
  1.      enable = yesnotoi(o->ptr);  
  2.   
  3.      (enable == -1)  badfmt;  
  4.      (enable == 0 && server.aof_state != REDIS_AOF_OFF) {  
  5.         stopAppendOnly();  
  6.     }   (enable && server.aof_state == REDIS_AOF_OFF) {  
  7.          (startAppendOnly() == REDIS_ERR) {  
  8.             addReplyError(c,  
  9.                 );  
  10.             ;  
  11.         }  
  12.     }  
  13. }  
[cpp] 在CODE上查看代码片

派生到我的代码片

 
     startAppendOnly() {  
  1.     server.aof_last_fsync = server.unixtime;  
  2.     server.aof_fd = open(server.aof_filename,0644);  
  3.     redisAssert(server.aof_state == REDIS_AOF_OFF);  
  4.      (server.aof_fd == -1) {  
  5.         redisLog(REDIS_WARNING,,strerror(errno));  
  6.          REDIS_ERR;  
  7.     }  
  8.      (rewriteAppendOnlyFileBackground() == REDIS_ERR) {  
  9.         close(server.aof_fd);  
  10.         redisLog(REDIS_WARNING,);  
  11.          REDIS_ERR;  
  12.     }  
  13.      
  14.   
  15.     server.aof_state = REDIS_AOF_WAIT_REWRITE;  
  16.      REDIS_OK;  
  17. }  

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] 在CODE上查看代码片

派生到我的代码片

 
     rewriteAppendOnlyFileBackground() {  
  1.     pid_t childpid;  
  2.       start;  
  3.   
  4.       
  5.      (server.aof_child_pid != -1)  REDIS_ERR;  
  6.     start = ustime();  
  7.      ((childpid = fork()) == 0) {  
  8.          tmpfile[256];  
  9.   
  10.           
  11.         closeListeningSockets(0);  
  12.         redisSetProcTitle();  
  13.         snprintf(tmpfile,256,, () getpid());  
  14.          (rewriteAppendOnlyFile(tmpfile) == REDIS_OK) {  
  15.              private_dirty = zmalloc_get_private_dirty();  
  16.   
  17.              (private_dirty) {  
  18.                 redisLog(REDIS_NOTICE,  
  19.                     ,  
  20.                     private_dirty/(1024*1024));  
  21.             }  
  22.             exitFromChild(0);  
  23.         }  {  
  24.             exitFromChild(1);  
  25.         }  
  26.     }  {  
  27.           
  28.         server.stat_fork_time = ustime()-start;  
  29.          (childpid == -1) {  
  30.             redisLog(REDIS_WARNING,  
  31.                 ,  
  32.                 strerror(errno));  
  33.              REDIS_ERR;  
  34.         }  
  35.         redisLog(REDIS_NOTICE,  
  36.             ,childpid);  
  37.         server.aof_rewrite_scheduled = 0;  
  38.         server.aof_rewrite_time_start = time(NULL);  
  39.         server.aof_child_pid = childpid;  
  40.         updateDictResizePolicy();  
  41.          
  42.  
  43.  
  44.   
  45.         server.aof_selected_db = -1;  
  46.         replicationScriptCacheFlush();  
  47.          REDIS_OK;  
  48.     }  
  49.      REDIS_OK;   
  50. }  

接下来介绍rewriteAppendOnlyFile函数,该函数的主要工作为:遍历所有数据库中的数据,将其写入到临时文件temp-rewriteaof-%d.aof中,写入函数定义在rio.c中,比较简单,然后将数据刷新到硬盘中,然后将文件名rename为其调用者给定的临时文件名,注意仔细看代码,这里并没有修改为正式的AOF文件名。

在写入文件时如果设置server.aof_rewrite_incremental_fsync参数,那么在rioWrite函数中fwrite部分数据就会将数据fsync到硬盘中,来保证数据的正确性。
[cpp] 在CODE上查看代码片

派生到我的代码片

 
     rewriteAppendOnlyFile( *filename) {  
  1.     dictIterator *di = NULL;  
  2.     dictEntry *de;  
  3.     rio aof;  
  4.      *fp;  
  5.      tmpfile[256];  
  6.      j;  
  7.       now = mstime();  
  8.   
  9.      
  10.   
  11.     snprintf(tmpfile,, () getpid());  
  12.     fp = fopen(tmpfile,);  
  13.      (!fp) {  
  14.         redisLog(REDIS_WARNING, , strerror(errno));  
  15.          REDIS_ERR;  
  16.     }  
  17.   
  18.     rioInitWithFile(&aof,fp);   
  19.     io.file.autosync = bytes;每32M刷新一次  
  20.      (server.aof_rewrite_incremental_fsync)  
  21.         rioSetAutoSync(&aof,REDIS_AOF_AUTOSYNC_BYTES);  
  22.      (j = 0; j < server.dbnum; j++) {  
  23.          selectcmd[] = ;  
  24.         redisDb *db = server.db+j;  
  25.         dict *d = db->dict;  
  26.          (dictSize(d) == 0) ;  
  27.         di = dictGetSafeIterator(d);  
  28.          (!di) {  
  29.             fclose(fp);  
  30.              REDIS_ERR;  
  31.         }  
  32.   
  33.           
  34.          (rioWrite(&aof,selectcmd,(selectcmd)-1) == 0)  werr;  
  35.          (rioWriteBulkLongLong(&aof,j) == 0)  werr;  
  36.   
  37.           
  38.         ((de = dictNext(di)) != NULL) {  
  39.             sds keystr;  
  40.             robj key, *o;  
  41.               expiretime;  
  42.   
  43.             keystr = dictGetKey(de);  
  44.             o = dictGetVal(de);  
  45.             initStaticStringObject(key,keystr);  
  46.   
  47.             expiretime = getExpire(db,&key);  
  48.   
  49.               
  50.              (expiretime != -1 && expiretime < now) ;  
  51.   
  52.               
  53.              (o->type == REDIS_STRING) {  
  54.                   
  55.                  cmd[]=;  
  56.                  (rioWrite(&aof,(cmd)-1) == 0)  werr;  
  57.                   
  58.                  (rioWriteBulkObject(&aof,&key) == 0)  werr;  
  59.                  (rioWriteBulkObject(&aof,o) == 0)  werr;  
  60.             }   (o->type == REDIS_LIST) {  
  61.                  (rewriteListObject(&aof,&key,o) == 0)  werr;  
  62.             }   (o->type == REDIS_SET) {  
  63.                  (rewriteSetObject(&aof,o) == 0)  werr;  
  64.             }   (o->type == REDIS_ZSET) {  
  65.                  (rewriteSortedSetObject(&aof,o) == 0)  werr;  
  66.             }   (o->type == REDIS_HASH) {  
  67.                  (rewriteHashObject(&aof,o) == 0)  werr;  
  68.             }  {  
  69.                 redisPanic();  
  70.             }  
  71.               
  72.              (expiretime != -1) {  
  73.                  cmd[]=;  
  74.                  (rioWrite(&aof,(cmd)-1) == 0)  werr;  
  75.                  (rioWriteBulkObject(&aof,&key) == 0)  werr;  
  76.                  (rioWriteBulkLongLong(&aof,expiretime) == 0)  werr;  
  77.             }  
  78.         }  
  79.         dictReleaseIterator(di);  
  80.     }  
  81.   
  82.       
  83.     fflush(fp);  
  84.     aof_fsync(fileno(fp));  
  85.     fclose(fp);  
  86.   
  87.      
  88.   
  89.      (rename(tmpfile,filename) == -1) {  
  90.         redisLog(REDIS_WARNING,, strerror(errno));  
  91.         unlink(tmpfile);  
  92.          REDIS_ERR;  
  93.     }  
  94.     redisLog(REDIS_NOTICE,);  
  95.      REDIS_OK;  
  96.   
  97. werr:  
  98.     fclose(fp);  
  99.     unlink(tmpfile);  
  100.     redisLog(REDIS_WARNING,, strerror(errno));  
  101.      (di) dictReleaseIterator(di);  
  102.      REDIS_ERR;  
  103. }  

AOF rewrite工作到这里已经结束一半,上一篇文章提到如果server.aof_state != REDIS_AOF_OFF,那么就会将客户端请求指令修改的数据通过feedAppendOnlyFile函数追加到AOF文件中,那么此时AOF已经rewrite了,必须要处理此时出现的差异数据,记得在feedAppendOnlyFile函数中有这么一段代码

[cpp] 在CODE上查看代码片

派生到我的代码片

 
     (server.aof_child_pid != -1)  
  1.         aofRewriteBufferAppend((unsigned *)buf,sdslen(buf));  

如果AOF rewrite正在进行,那么就将修改数据的指令字符串存储到server.aof_rewrite_buf_blocks链表中,等待AOF rewrite子进程结束后处理,处理此部分数据的代码在serverCron函数中。需要指出的是wait3函数我不了解,可能下面注释会有点问题。

[cpp] 在CODE上查看代码片

派生到我的代码片

 
      
  1.   
  2.  (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {  
  3.      statloc;  
  4.     pid_t pid;  
  5.   
  6.      ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {  
  7.          exitcode = WEXITSTATUS(statloc);  
  8.          bysignal = 0;  
  9.   
  10.          (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);  
  11.   
  12.          (pid == server.rdb_child_pid) {  
  13.             backgroundSaveDoneHandler(exitcode,bysignal);  
  14.         }   (pid == server.aof_child_pid) {  
  15.             backgroundRewriteDoneHandler(exitcode,bysignal);  
  16.         }  {  
  17.             redisLog(REDIS_WARNING,  
  18.                 ,  
  19.                 ()pid);  
  20.         }  
  21.           
  22.         updateDictResizePolicy();  
  23.     }  
  24. }  

对于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] 在CODE上查看代码片

派生到我的代码片

 
     
  1.   
  2.  backgroundRewriteDoneHandler( exitcode,  bysignal) {  
  3.      (!bysignal && exitcode == 0) {  
  4.          newfd, oldfd;  
  5.          tmpfile[256];  
  6.           now = ustime();  
  7.   
  8.         redisLog(REDIS_NOTICE,  
  9.             );  
  10.   
  11.          
  12.   
  13.         snprintf(tmpfile,  
  14.             ()server.aof_child_pid);  
  15.         newfd = open(tmpfile,O_WRONLY|O_APPEND);  
  16.          (newfd == -1) {  
  17.             redisLog(REDIS_WARNING,  
  18.                 , strerror(errno));  
  19.              cleanup;  
  20.         }  
  21.           
  22.          (aofRewriteBufferWrite(newfd) == -1) {  
  23.             redisLog(REDIS_WARNING,  
  24.                 , strerror(errno));  
  25.             close(newfd);  
  26.              cleanup;  
  27.         }  
  28.   
  29.         redisLog(REDIS_NOTICE,  
  30.             , aofRewriteBufferSize());  
  31.   
  32.          
  33.  
  34.  
  35.  
  36.  
  37.  
  38.  
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.   
  59.          (server.aof_fd == -1) {  
  60.               
  61.   
  62.               
  63.  
  64.   
  65.              oldfd = open(server.aof_filename,O_RDONLY|O_NONBLOCK);  
  66.         }  {  
  67.               
  68.             oldfd = -1;   
  69.         }  
  70.   
  71.          
  72.   
  73.           
  74.           
  75.          (rename(tmpfile,server.aof_filename) == -1) {  
  76.             redisLog(REDIS_WARNING,  
  77.                 , strerror(errno));  
  78.             close(newfd);  
  79.              (oldfd != -1) close(oldfd);  
  80.              cleanup;  
  81.         }  
  82.           
  83.           
  84.           
  85.          (server.aof_fd == -1) {  
  86.              
  87.   
  88.             close(newfd);  
  89.         }  {  
  90.               
  91.             oldfd = server.aof_fd;  
  92.               
  93.             server.aof_fd = newfd;  
  94.               
  95.              (server.aof_fsync == AOF_FSYNC_ALWAYS)  
  96.                 aof_fsync(newfd);  
  97.               (server.aof_fsync == AOF_FSYNC_EVERYSEC)  
  98.                 aof_background_fsync(newfd);  
  99.             server.aof_selected_db = -1;   
  100.             aofUpdateCurrentSize();  
  101.             server.aof_rewrite_base_size = server.aof_current_size;  
  102.   
  103.              
  104.   
  105.               
  106.             sdsfree(server.aof_buf);  
  107.             server.aof_buf = sdsempty();  
  108.         }  
  109.   
  110.         server.aof_lastbgrewrite_status = REDIS_OK;  
  111.   
  112.         redisLog(REDIS_NOTICE, );  
  113.           
  114.           
  115.          (server.aof_state == REDIS_AOF_WAIT_REWRITE)  
  116.             server.aof_state = REDIS_AOF_ON;  
  117.   
  118.           
  119.           
  120.          (oldfd != -1) bioCreateBackgroundJob(REDIS_BIO_CLOSE_FILE,(*)()oldfd,NULL);  
  121.   
  122.         redisLog(REDIS_VERBOSE,  
  123.             , ustime()-now);  
  124.     }   (!bysignal && exitcode != 0) {  
  125.         server.aof_lastbgrewrite_status = REDIS_ERR;  
  126.   
  127.         redisLog(REDIS_WARNING,  
  128.             );  
  129.     }  {  
  130.         server.aof_lastbgrewrite_status = REDIS_ERR;  
  131.   
  132.         redisLog(REDIS_WARNING,  
  133.             , bysignal);  
  134.     }  
  135.   
  136. cleanup:  
  137.     aofRewriteBufferReset();  
  138.     aofRemoveTempFile(server.aof_child_pid);  
  139.     server.aof_child_pid = -1;  
  140.     server.aof_rewrite_time_last = time(NULL)-server.aof_rewrite_time_start;  
  141.     server.aof_rewrite_time_start = -1;  
  142.       
  143.      (server.aof_state == REDIS_AOF_WAIT_REWRITE)  
  144.         server.aof_rewrite_scheduled = 1;  
  145. }  
 

至此,AOF数据持久化已经全部结束了,剩下的就是一些细节的处理,以及一些Linux库函数的理解,对于rename、unlink、wait3等库函数的深入认识就去问Google吧。

 

小结

 
Redis AOF数据持久化的实现机制通过三篇文章基本上比较详细的分析了,但这只是从代码层面去看AOF,对于AOF持久化的优缺点网上有很多分析,Redis的官方网站也有英文介绍,Redis的数据持久化还有一种方法叫RDB,更多RDB的内容等下次再分析。
感谢此篇博客给我在理解Redis AOF数据持久化方面的巨大帮助,,此篇博客对AOF的分析十分的详细。

总结

以上是脚本之家为你收集整理的Redis数据持久化机制AOF原理分析一---转全部内容,希望文章能够帮你解决Redis数据持久化机制AOF原理分析一---转所遇到的程序开发问题。

如果觉得脚本之家网站内容还不错,欢迎将脚本之家网站推荐给程序员好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您喜欢交流学习经验,点击链接加入脚本之家官方QQ群:1065694478
猜你在找的Redis相关文章
分类导航
脚本之家官方公众号

微信公众号搜索 “ 程序精选 ” ,选择关注!

微信公众号搜索 “ 程序精选 ”
精选程序员所需精品干货内容!