IT技术博客大学习 共学习 共进步

redis源代码分析 - persistence

运维和开发 2011-07-16 20:47:57 浏览 32,104 次

    redis有全量(save/bgsave)和增量(aof)的持久化命令。

    全量的原理就是遍历里所有的DB,在每个bucket,读取链表的key和value并写入dump.rdb文件(rdb.c 405)。

    save命令直接调度rdbSave函数,这会阻塞主线程的工作,通常我们使用bgsave。

    bgsave命令调度rdbSaveBackground函数启动了一个子进程然后调度了rdbSave函数,子进程的退出状态由 serverCron的backgroundSaveDoneHandler来判断,这个在复制这篇文章里讲到过,这里就不罗嗦了。

    除了直接的save、bgsave命令之外,还有几个地方还调用到rdbSaveBackground和rdbSave函数。

    shutdown:redis关闭时需要对数据持久化,保证重启后数据一致性,会调用rdbSave()。

    flushallCommand:清空redis数据后,如果不做立即执行一个rdbSave(),出现crash后,可能会载入含有老数据的dump.rdb。

void flushallCommand(redisClient *c) {
  touchWatchedKeysOnFlush(-1);
  server.dirty += emptyDb();      // 清空数据
  addReply(c,shared.ok);
  if (server.bgsavechildpid != -1) {
    kill(server.bgsavechildpid,SIGKILL);
    rdbRemoveTempFile(server.bgsavechildpid);
  }
  rdbSave(server.dbfilename);    //没有数据的dump.db
  server.dirty++;
}

    sync:当master接收到slave发来的该命令的时候,会执行rdbSaveBackground,这个以前也有提过。

    数据发生变化:在多少秒内出现了多少次变化则触发一次bgsave,这个可以在conf里配置

for (j = 0; j < server.saveparamslen; j++) {
  struct saveparam *sp = server.saveparams+j;
   if (server.dirty >= sp->changes && now-server.lastsave > sp->seconds) {
     rdbSaveBackground(server.dbfilename);
     break;
  }
}

    增量备份就是aof,原理有点类似redo log。每次执行命令后如出现数据变化,会调用feedAppendOnlyFile,把数据写到server.aofbuf里。

void call(redisClient *c, struct redisCommand *cmd) {
  long long dirty;
  dirty = server.dirty;
  cmd->proc(c);        //执行命令
  dirty = server.dirty-dirty;
  if (server.appendonly && dirty)
    feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc);

    待到下次循环的before_sleep函数会通过flushAppendOnlyFile函数把server.aofbuf里的数据write到append file里。

     可以在redis.conf里配置每次write到append file后从page cache刷新到disk的规律。

# appendfsync always
appendfsync everysec
# appendfsync no

    参数的原理MySQL的innodb_flush_log_at_trx_commit一样,是个比较影响io的一个参数,需要在高性能和不丢数据之间做tradeoff。软件的优化就是tradeoff的过程,没有银弹。

    一个疑问先写到server.aofbuf,然后再写到数据文件,过程中如果crash会不会丢数据呢?

    答案是不会,为何?我们来看函数执行的步骤:

call()
feedAppendOnlyFile()
下一次循环
beforeSleep()-->flushAppendOnlyFile()
aeMain()--->sendReplyToClient()

    只有执行完了flush之后才会通知客户端数据写成功了,所以如果在feed和flush之间crash,客户接会因为进程退出接受到一个fin包,也就是一个错误的返回,所以数据并没有丢,只是执行出了错。

    redis crash后,重启除了利用rdb重新载入数据外,还会读取append file(redis.c 1561)加载镜像之后的数据。

    激活aof,可以在redis.conf配置文件里设置

appendonly yes

    也可以通过config命令在运行态启动aof

cofig set appendonly yes

    aof最大的问题就是随着时间append file会变的很大,所以我们需要bgrewriteaof命令重新整理文件,只保留最新的kv数据。

    下面这个根据图形来描述一下aof的全过程,绿色的为aof.c里的函数,顺序从左到右。

    \"\"

    启动appendly,或者config set appendonly yes 都会触发函数rewriteAppendOnlyFileBackground,bgrewriteaof也调用该函数。

    实际最后调用的函数是rewriteAppendOnlyFile,这个函数与rdbSave和类似。保存全库的kv数据。

    在子进程做快照的过程中,kv的变化是先写到aofbuf里。如果存在bgrewritechildpid进程,变化数据还要写到server.bgrewritebuf里(aof.c 177)。

     等子进程完成快照退出之时,由backgroundRewriteDoneHandler函数再把bgrwritebuf和全镜像两部分数据进行合并(aof.c 673)。

    合并后的aof文件才是最新的全库的镜像数据。

    ps:在最新的2.2.11版本的stopAppendOnly函数里存在一个bug,应该杀的进程是bgrewriteachilidpid进程,bgsavechildpid不不幸中枪,和Salvatore Sanfilippo邮件交流了一下,他承认了这个bug。 说2.2版本忘记改了。

建议继续学习

  1. Redis消息队列的若干实现方式 (阅读 11,926)
  2. 基于Redis构建系统的经验和教训 (阅读 10,383)
  3. 浅谈redis数据库的键值设计 (阅读 9,222)
  4. redis运维的一些知识点 (阅读 8,521)
  5. redis在大数据量下的压测表现 (阅读 8,202)
  6. Redis和Memcached的区别 (阅读 7,943)
  7. redis 运维实际经验纪录之一 (阅读 7,583)
  8. Redis作者谈Redis应用场景 (阅读 7,543)
  9. 记Redis那坑人的HGETALL (阅读 7,323)
  10. 让Redis使用TCMalloc,实现高性能NOSql服务器 (阅读 7,203)