Redis源碼剖析之AOF
書接上回,上回我們詳細(xì)講解了Redis的RDB機(jī)制,RDB解決了redis數(shù)據(jù)持久化一部分的問題,為什么說一部分?因?yàn)閞db是redis中某一時刻的快照,那么在這次快照后如果數(shù)據(jù)有新的變更,它是不會被持久化下來的,必須得等到下次rdb備份。然而,生成rdb是和消耗性能的,所以它就不適合很頻繁生成。Redis為了彌補(bǔ)這一不足提供了AOF。

AOF的全稱是AppendOnlyFile,源碼在aof.c。其實(shí)關(guān)鍵就是Append(追加),核心原理很簡單,就是如果執(zhí)行完命令(set,del,expire……)后,發(fā)現(xiàn)有數(shù)據(jù)變動,就將這次操作作為一條日志記錄到aof文件里,如果有宕機(jī)就重新加載aof文件,重放所有的改動命令就可以恢復(fù)數(shù)據(jù)了。只要日志被完整刷到了磁盤上,數(shù)據(jù)就不會丟失。
配置
AOF的配置比較簡單,只有如下幾項(xiàng)。
appendonly no # aof開關(guān),默認(rèn)關(guān)閉 appendfilename "appendonly.aof" # 保存的文件名,默認(rèn)appendonly.aof # 有三種刷數(shù)據(jù)的策略 appendfsync always # always是只要有數(shù)據(jù)改動,就把數(shù)據(jù)刷到磁盤里,最安全但性能也最差 appendfsync everysec # 每隔一秒鐘刷一次數(shù)據(jù),數(shù)據(jù)安全性和性能折中,這也是redis默認(rèn)和推薦的配置。 appendfsync no # 不主動刷,什么時候數(shù)據(jù)刷到磁盤里取決于操作系統(tǒng),在大多數(shù)Linux系統(tǒng)中每30秒提交一次,性能最好,但數(shù)據(jù)安全性最差。
源碼
AOF的觸發(fā)
aof如何實(shí)現(xiàn),又是怎么被觸發(fā)的,讓我們詳細(xì)看下源碼。
server.c中的void call(client *c, int flags)是redis接受到client請求后處理請求的入口,其中會檢測Redis中的數(shù)據(jù)有沒有發(fā)生變化。如果有變化就會執(zhí)行propagate()函數(shù)。
dirty = server.dirty; prev_err_count = server.stat_total_error_replies; updateCachedTime(0); elapsedStart(&call_timer); c->cmd->proc(c); // 執(zhí)行命令 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函數(shù)的作用就是將帶來數(shù)據(jù)改動的命令傳播給slave和AOF,這里我們只關(guān)注AOF,我們來詳細(xì)看下feedAppendOnlyFile()函數(shù)。
AOF數(shù)據(jù)生成
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 命令轉(zhuǎn)化為 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所帶的相對時間轉(zhuǎn)化為絕對時間(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 { /* 其他的命令都不需要轉(zhuǎn)化 */ buf = catAppendOnlyGenericCommand(buf,argc,argv); } /* 追加到AOF緩沖區(qū)。在重新進(jìn)入事件循環(huán)之前,數(shù)據(jù)將被刷新到磁盤上,因此在客戶端在執(zhí)行前就會得到回復(fù)。*/ if (server.aof_state == AOF_ON) server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf)); /* 如果后臺正在進(jìn)行AOF重寫,我們希望將子數(shù)據(jù)庫和當(dāng)前數(shù)據(jù)庫之間的差異累積到緩沖區(qū)中, * 以便在子進(jìn)程執(zhí)行其工作時,我們可以將這些差異追加到新的只追加文件中。 */ if (server.child_type == CHILD_TYPE_AOF) aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); sdsfree(buf); }
這里沒有啥太復(fù)雜的邏輯,就是將命令轉(zhuǎn)化為RESP協(xié)議格式的字符串(RESP協(xié)議后續(xù)會詳解),然后追加到server.aof_buf中,這時候AOF數(shù)據(jù)還都在緩沖區(qū)中,并沒有寫入到磁盤中,那buf中的數(shù)據(jù)何時寫入磁盤呢?
刷數(shù)據(jù)
刷數(shù)據(jù)的核心代碼在flushAppendOnlyFile()中,flushAppendOnlyFile在serverCron、beforeSleep和prepareForShutdown中都有被調(diào)用,它的作用就是將緩沖區(qū)的數(shù)據(jù)寫到磁盤中,代碼比較長且復(fù)雜,但大部分都是異常處理和性能監(jiān)控,忽略掉這部分后代碼也比較容易理解,這里就不再羅列了,詳見aof.c。
RDB vs AOF
最后,我們來對比下RDB和AOF,他們各自都有啥優(yōu)缺點(diǎn),該如何選用。
RDB的優(yōu)勢
RDB是壓縮的后緊湊數(shù)據(jù)格式,比較很適合備份,
同樣的數(shù)據(jù)量下,rdb的文件大小會很小,比較適合傳輸和數(shù)據(jù)恢復(fù)。
RDB對Redis的讀寫性能影響小,生成RDB的時redis主進(jìn)程會fork出一個子進(jìn)程,不會影響到主進(jìn)程的讀寫。
RDB數(shù)據(jù)加載更快,恢復(fù)起來更快。
RDB的缺點(diǎn)
RDB是定期備份,如果備份前發(fā)生宕機(jī),數(shù)據(jù)可能會丟失。
RDB的生成依賴于linux的fork,如果數(shù)據(jù)量比較大的話,很影響服務(wù)器性能。
AOF的優(yōu)勢
AOF是持續(xù)性備份,可以盡可能保證數(shù)據(jù)不丟失。
Redis太大時,Redis可以在后臺自動重寫AOF。重寫是完全安全的,因?yàn)镽edis繼續(xù)追加到舊文件時,會生成一個全新的文件,其中包含創(chuàng)建當(dāng)前數(shù)據(jù)集所需的最少操作集,一旦準(zhǔn)備好第二個文件,Redis會切換這兩個文件并開始追加到新的那一個。
AOF文件格式簡單,易于解析。
AOF的缺點(diǎn)
對于同一數(shù)據(jù)集,AOF文件大小通常大于等效的RDB文件。
如果使用fsync策略,AOF可能比RDB慢。
RDB和AOF該如何選
如果是要求極致的性能,但對數(shù)據(jù)恢復(fù)不敏感,二者可以都不要,如果是關(guān)注性能且關(guān)注數(shù)據(jù)可用性,但不要求數(shù)據(jù)完整性,可以選用RDB。如果說非常關(guān)注數(shù)據(jù)完整性和宕機(jī)恢復(fù)的能力,可以RDB+AOF同時開啟。
參考資料
Redis persistence demystified
Redis Persistence
本文是Redis源碼剖析系列博文,同時也有與之對應(yīng)的Redis中文注釋版,有想深入學(xué)習(xí)Redis的同學(xué),歡迎star和關(guān)注。
Redis中文注解版?zhèn)}庫:https://github.com/xindoo/Redis
Redis源碼剖析專欄:https://zxs.io/s/1h
如果覺得本文對你有用,歡迎一鍵三連。
Redis
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。