REDIS的ANAPSHOT实现

Redis SNAPSHOT

上一篇文章我们学习了redis aof的实现.这篇文章我们将学习redis的另一种持久化方式:snapshot(快照)。
同上一篇文章一样,我们首先介绍相关参数;然后依次介绍它的使用场景。

1. 配置参数
save <seconds> <changes>:相对一个DB,多少秒内发生了多少次更新操作,此时就会进行一次保存操作,这个可以设置多个条件,它们中的任一个满足都会保存一次,下面把这个配置叫做时间变化条件。
rdbcompression yes:是否进行压缩操作
dbfilename dump.rdb:数据文件的名字
dir /u01/xiangzhong/redis-2.4.2/data:上面文件保存的位置

2.使用场景
通过上面的参数save <seconds> <changes>我们可以知道当触发这个条件的时候就会执行一次save rdb操作。所以我们先来看一下这个过程。
2.1 save <seconds> <changes>条件触发自动运行
该过程在serverCron里判断

[cpp]  view plain copy
  1. /* If there is not a background saving in progress check if 
  2.  * we have to save now */  
  3.  for (j = 0; j < server.saveparamslen; j++) {//判断总共有多少个时间变化条件,这个总共由三部分组成,系统启动时initServerConfig初始化三个[(60*60,1),(300,100),(60,10000)];然后就是加载配置文件loadServerConfig;最后就是client通过命令来添加条件;它们被保存到server.saveparams这个动态数组里,在追加的时候并没有对覆盖情况进行去重,不过这个数据不会太大,所以这个影响不大  
  4.     struct saveparam *sp = server.saveparams+j;  
  5.   
  6.     if (server.dirty >= sp->changes &&  
  7.         now-server.lastsave > sp->seconds) { //这里就是简单的每个条件匹配  
  8.         redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",  
  9.             sp->changes, sp->seconds);  
  10.         rdbSaveBackground(server.dbfilename); //生成后台子进程进行写操作  
  11.         break;  
  12.     }  
  13.  }  
这里我们不再介绍rdbSaveBackground函数,而直接介绍子进程的处理rdbSave:
[cpp]  view plain copy
  1. int rdbSave(char *filename) {  
  2. ...  
  3.     snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid()); //生成一个新的临时文件  
  4.     fp = fopen(tmpfile,"w");  
  5.     if (fwrite("REDIS0002",9,1,fp) == 0) goto werr;  
  6.     for (j = 0; j < server.dbnum; j++) {  
  7.         redisDb *db = server.db+j;  
  8.         dict *d = db->dict;  
  9.         if (dictSize(d) == 0) continue;  
  10.         di = dictGetSafeIterator(d);  
  11.         //写select db 命令,只是这里使用的是编码的方式,而不是可读的字符命令  
  12.         if (rdbSaveType(fp,REDIS_SELECTDB) == -1) goto werr;  
  13.         if (rdbSaveLen(fp,j) == -1) goto werr;//db id  
  14.   
  15.         while((de = dictNext(di)) != NULL) { //遍历该db内的所有表的所有记录entry  
  16.             sds keystr = dictGetEntryKey(de); //获得key值  
  17.             robj key, *o = dictGetEntryVal(de); //获得value  
  18.             time_t expiretime;  
  19.               
  20.             initStaticStringObject(key,keystr); //key有两种可能encode:RAW,INT这里直接使用RAW,只有是数据的才可能被encode为INT,所以RAW是安全,可靠的  
  21.   
  22.             expiretime = getExpire(db,&key);  
  23.             if (expiretime != -1) {  
  24.                 //判断该key是否已经过期,如果过期了就不保存了直接跳过  
  25.                 if (expiretime < now) continue;  
  26.                 if (rdbSaveType(fp,REDIS_EXPIRETIME) == -1) goto werr;  
  27.                 if (rdbSaveTime(fp,expiretime) == -1) goto werr;  
  28.             }  
  29.             if (!server.vm_enabled || o->storage == REDIS_VM_MEMORY ||  
  30.                                       o->storage == REDIS_VM_SWAPPING) {  
  31.                 int otype = getObjectSaveType(o); //获得value的类型  
  32.   
  33.                 /* Save type, key, value */  
  34.                 if (rdbSaveType(fp,otype) == -1) goto werr;  
  35.                 if (rdbSaveStringObject(fp,&key) == -1) goto werr;  
  36.                 if (rdbSaveObject(fp,o) == -1) goto werr;  
  37.             }  
  38.      ...  
  39.         } //end while dict entry  
  40.   dictReleaseIterator(di);  
  41.     } //end for db  
  42.     if (rdbSaveType(fp,REDIS_EOF) == -1) goto werr;  
  43.     fflush(fp);  
  44.     fsync(fileno(fp));  
  45.     fclose(fp);  
  46.     rename(tmpfile,filename);  
  47.     server.dirty = 0;  //对于子进程这两个值更新没有作用,但如果是使用save命令,则它是在主线程里进行所以必须更新  
  48.     server.lastsave = time(NULL);  
  49.     ...  
  50. }  
该函数相对于aof的rewriteAppendOnlyFile简单得多,里面的注释也很清楚,关于value的type与encode可参考: http://www.w3ccollege.org/redis/redis-internal/redis-memory-storage-structure-analysis-2.html
当后台子进程结束的时候,主线程在serverCron里wait该信号(这与aof的时机一样),我们这里直接看一下信号处理函数backgroundSaveDoneHandler
[cpp]  view plain copy
  1. server.dirty = server.dirty - server.dirty_before_bgsave; //更新dirty  
  2. server.lastsave = time(NULL);  
  3. server.bgsavechildpid = -1;  
  4. updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR); //这个与repl slave有关,暂不介绍  

2.2客户端命令触发
上面我们介绍了系统自动检测时间改变条件,完成save rdb的过程,下面我们将介绍另外两种情况它们都是由客户端通过命令来触发的:save、bgsave。
A.save
该命令的回调函数为:saveCommand
[cpp]  view plain copy
  1. void saveCommand(redisClient *c) { //如果现在后面刚好有一个进程在save,显然不再需要了,另外该函数也是通过调用rdbSave来实现的。  
  2.     if (server.bgsavechildpid != -1) {  
  3.         addReplyError(c,"Background save already in progress");  
  4.         return;  
  5.     }  
  6.     if (rdbSave(server.dbfilename) == REDIS_OK) {  
  7.         addReply(c,shared.ok);  
  8.     } else {  
  9.         addReply(c,shared.err);  
  10.     }  
  11. }  
注:该方式是在主线程中执行save rdb操作,所以会阻塞主线程的工作。所以一般不建议使用该命令,而使用下面的命令。

B.bgsave
该命令的回调函数为:bgsaveCommand,该函数也是通过调用rdbSaveBackground来实现,即其实这个过程跟上面的server判断时间改变条件的过程是一样,只是后者是由客户端通过命令来强制它执行,而不是像前者那样等到条件满足时才执行。所以我们这里也不再赘述。

3.总结
redis的SNAPSHOT有三种应用场景:其一在每次运行serverCron的时候去检测时间改变条件是否满足,如果满足就会创建一个后台子进程进行内存数据到dump.rdb文件的写过程:将内存数据写到一个tempfile,然后再rename(也就是每次都是把整个内存数据保存起来,而不是修改更新的数据);客户端发送save命令,此时是在主线程中执行的,所以会阻塞主线对file event的响应;最后一种跟第一种很相似只是它是通过客户端发送bgsave来实现。另外之所以称为SNAPSHOT,是因为它是利用fork子进程与父进程是通过copy-on-write的方式来暂时共享内存地址空间的,而不影响父进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值