书接上回,上回咱们详细讲解了Redis的RDB机制,RDB解决了redis数据持久化一部分的问题,为何说一部分?由于rdb是redis中某一时刻的快照,那么在此次快照后若是数据有新的变动,它是不会被持久化下来的,必须得等到下次rdb备份。然而,生成rdb是和消耗性能的,因此它就不适合很频繁生成。Redis为了弥补这一不足提供了AOF。 html
AOF的全称是AppendOnlyFile,源码在aof.c。其实关键就是Append(追加),核心原理很简单,就是若是执行完命令(set,del,expire……)后,发现有数据变更,就将此次操做做为一条日志记录到aof文件里,若是有宕机就从新加载aof文件,重放全部的改动命令就能够恢复数据了。只要日志被完整刷到了磁盘上,数据就不会丢失。linux
AOF的配置比较简单,只有以下几项。git
appendonly no # aof开关,默认关闭 appendfilename "appendonly.aof" # 保存的文件名,默认appendonly.aof # 有三种刷数据的策略 appendfsync always # always是只要有数据改动,就把数据刷到磁盘里,最安全但性能也最差 appendfsync everysec # 每隔一秒钟刷一次数据,数据安全性和性能折中,这也是redis默认和推荐的配置。 appendfsync no # 不主动刷,何时数据刷到磁盘里取决于操做系统,在大多数Linux系统中每30秒提交一次,性能最好,但数据安全性最差。
aof如何实现,又是怎么被触发的,让咱们详细看下源码。
server.c中的void call(client *c, int flags)
是redis接受到client请求后处理请求的入口,其中会检测Redis中的数据有没有发生变化。若是有变化就会执行propagate()函数。github
dirty = server.dirty; prev_err_count = server.stat_total_error_replies; updateCachedTime(0); elapsedStart(&call_timer); c->cmd->proc(c); // 执行命令 const long duration = elapsedUs(call_timer); c->duration = duration; dirty = server.dirty-dirty; if (dirty < 0) dirty = 0;
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags) { if (server.in_exec && !server.propagate_in_transaction) execCommandPropagateMulti(dbid); /* This needs to be unreachable since the dataset should be fixed during * client pause, otherwise data may be lossed during a failover. */ serverAssert(!(areClientsPaused() && !server.client_pause_in_transaction)); if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF) feedAppendOnlyFile(cmd,dbid,argv,argc); // 若是aof开启了,就会向aof传播该命令。 if (flags & PROPAGATE_REPL) replicationFeedSlaves(server.slaves,dbid,argv,argc); }
propagate函数的做用就是将带来数据改动的命令传播给slave和AOF,这里咱们只关注AOF,咱们来详细看下feedAppendOnlyFile()函数。redis
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) { sds buf = sdsempty(); /* The DB this command was targeting is not the same as the last command * we appended. To issue a SELECT command is needed. */ if (dictid != server.aof_selected_db) { char seldb[64]; snprintf(seldb,sizeof(seldb),"%d",dictid); buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n", (unsigned long)strlen(seldb),seldb); server.aof_selected_db = dictid; } if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || cmd->proc == expireatCommand) { /* 把 EXPIRE/PEXPIRE/EXPIREAT 命令转化为 PEXPIREAT 命令*/ buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); } else if (cmd->proc == setCommand && argc > 3) { robj *pxarg = NULL; /* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument. * So since the command arguments are re-written there, we can rely here on the index of PX being 3. */ if (!strcasecmp(argv[3]->ptr, "px")) { pxarg = argv[4]; } /* 把set命令的expired所带的相对时间转化为绝对时间(ms). */ if (pxarg) { robj *millisecond = getDecodedObject(pxarg); long long when = strtoll(millisecond->ptr,NULL,10); when += mstime(); decrRefCount(millisecond); robj *newargs[5]; newargs[0] = argv[0]; newargs[1] = argv[1]; newargs[2] = argv[2]; newargs[3] = shared.pxat; newargs[4] = createStringObjectFromLongLong(when); buf = catAppendOnlyGenericCommand(buf,5,newargs); decrRefCount(newargs[4]); } else { buf = catAppendOnlyGenericCommand(buf,argc,argv); } } else { /* 其余的命令都不须要转化 */ buf = catAppendOnlyGenericCommand(buf,argc,argv); } /* 追加到AOF缓冲区。在从新进入事件循环以前,数据将被刷新到磁盘上,所以在客户端在执行前就会获得回复。*/ if (server.aof_state == AOF_ON) server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); /* 若是后台正在进行AOF重写,咱们但愿将子数据库和当前数据库之间的差别累积到缓冲区中, * 以便在子进程执行其工做时,咱们能够将这些差别追加到新的只追加文件中。 */ if (server.child_type == CHILD_TYPE_AOF) aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); sdsfree(buf); }
这里没有啥太复杂的逻辑,就是将命令转化为RESP协议格式的字符串(RESP协议后续会详解),而后追加到server.aof_buf中,这时候AOF数据还都在缓冲区中,并无写入到磁盘中,那buf中的数据什么时候写入磁盘呢?数据库
刷数据的核心代码在flushAppendOnlyFile()
中,flushAppendOnlyFile在serverCron、beforeSleep和prepareForShutdown中都有被调用,它的做用就是将缓冲区的数据写到磁盘中,代码比较长且复杂,但大部分都是异常处理和性能监控,忽略掉这部分后代码也比较容易理解,这里就再也不罗列了,详见aof.c。安全
最后,咱们来对比下RDB和AOF,他们各自都有啥优缺点,该如何选用。服务器
AOF文件格式简单,易于解析。app
若是是要求极致的性能,但对数据恢复不敏感,两者能够都不要,若是是关注性能且关注数据可用性,但不要求数据完整性,能够选用RDB。若是说很是关注数据完整性和宕机恢复的能力,能够RDB+AOF同时开启。函数
本文是Redis源码剖析系列博文,同时也有与之对应的Redis中文注释版,有想深刻学习Redis的同窗,欢迎star和关注。
Redis中文注解版仓库: https://github.com/xindoo/Redis
Redis源码剖析专栏: https://zxs.io/s/1h
若是以为本文对你有用,欢迎 一键三连。