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文件才是最新的全库的镜像数据。