書接上回,上回咱們詳細講解了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
若是以爲本文對你有用,歡迎 一鍵三連。