Redis AOF 簡介
Redis AOF是類似于log的機(jī)制慌核,每次寫操作都會寫到硬盤上距境,當(dāng)系統(tǒng)崩潰時,可以通過AOF來恢復(fù)數(shù)據(jù)垮卓。每個帶有寫操作的命令被Redis服務(wù)器端收到運(yùn)行時垫桂,該命令都會被記錄到AOF文件上。由于只是一個append到文件操作粟按,所以寫到硬盤上的操作往往非澄芴玻快。
其實(shí)Redis oaf機(jī)制包括了兩件事灭将,rewrite和AOF疼鸟。rewrite類似于普通數(shù)據(jù)庫管理系統(tǒng)日志恢復(fù)點(diǎn),當(dāng)AOF文件隨著寫命令的運(yùn)行膨脹時庙曙,當(dāng)文件大小觸碰到臨界時空镜,rewrite會被運(yùn)行。
rewrite會像replication一樣,fork出一個子進(jìn)程姑裂,創(chuàng)建一個臨時文件,遍歷數(shù)據(jù)庫男旗,將每個key舶斧、value對輸出到臨時文件。輸出格式就是Redis的命令察皇,但是為了減小文件大小茴厉,會將多個key、value對集合起來用一條命令表達(dá)什荣。在rewrite期間的寫操作會保存在內(nèi)存的rewrite buffer中矾缓,rewrite成功后這些操作也會復(fù)制到臨時文件中,在最后臨時文件會代替AOF文件稻爬。
以上在AOF打開的情況下嗜闻,如果AOF是關(guān)閉的,那么rewrite操作可以通過bgrewriteaof命令來進(jìn)行桅锄。
Redis AOF流程
Redis Server啟動琉雳,如果AOF機(jī)制打開那么初始化AOF狀態(tài),并且如果存在AOF文件友瘤,讀取AOF文件翠肘。
隨著Redis不斷接受命令,每個寫命令都被添加到AOF文件辫秧,AOF文件膨脹到需要rewrite時又或者接收到客戶端的bgrewriteaof命令束倍。
fork出一個子進(jìn)程進(jìn)行rewrite,而父進(jìn)程繼續(xù)接受命令盟戏,現(xiàn)在的寫操作命令都會被額外添加到一個aof_rewrite_buf_blocks緩沖中绪妹。
當(dāng)子進(jìn)程rewrite結(jié)束后,父進(jìn)程收到子進(jìn)程退出信號抓半,把a(bǔ)of_rewrite_buf_blocks的緩沖添加到rewrite后的文件中喂急,然后切換AOF的文件fd。rewrite任務(wù)完成笛求,繼續(xù)第二個步驟廊移。
關(guān)鍵點(diǎn)
由于寫操作通常是有緩沖的,所以有可能AOF操作并沒有寫到硬盤中探入,一般可以通過fsync()來強(qiáng)制輸出到硬盤中狡孔。而fsync()的頻率可以通過配置文件中的flush策略來指定,可以選擇每次事件循環(huán)寫操作都強(qiáng)制fsync或者每秒fsync至少運(yùn)行一次蜂嗽。
當(dāng)rewrite子進(jìn)程開始后苗膝,父進(jìn)程接受到的命令會添加到aof_rewrite_buf_blocks中,使得rewrite成功后植旧,將這些命令添加到新文件中辱揭。在rewrite過程中离唐,原來的AOF也可以選擇是不是繼續(xù)添加,由于存在性能上的問題问窃,在rewrite過程中亥鬓,如果fsync()繼續(xù)執(zhí)行,會導(dǎo)致IO性能受損影響Redis性能域庇。所以一般情況下rewrite期間禁止fsync()到舊AOF文件嵌戈。這策略可以在配置文件中修改。
在rewrite結(jié)束后听皿,在將新rewrite文件重命名為配置中指定的文件時熟呛,如果舊AOF存在,那么會unlink掉舊文件尉姨。這是就存在一個問題庵朝,處理rewrite文件遷移的是主線程,rename(oldpath, newpath)過程會覆蓋舊文件啊送,這是rename會unlink(oldfd)偿短,而unlink操作會導(dǎo)致block主線程。這時馋没,我們就需要類似libeio(http://software.schmorp.de/pkg/libeio.html)這樣的庫去進(jìn)行異步的底層IO昔逗。作者在bio.c有一個類似的機(jī)制,通過創(chuàng)建新線程來進(jìn)行異步操作篷朵。
==========================================================================================================
- 自動的bgrewriteaof
為了避免aof文件過大勾怒,我們會周期性的做bgrewriteaof來重整aof文件。以前我們會額外的配置crontab在業(yè)務(wù)低峰期執(zhí)行這個命令声旺,這額外的增加一個workaroud的腳本任務(wù)在大集群里是很糟糕的笔链,不易檢查,出錯無法即時發(fā)現(xiàn)腮猖。
于是這個自動bgrewriteaof功能被直接加到redis的內(nèi)部鉴扫。首先對于aof文件,server對象添加一個字段來記錄aof文件的大小server.appendonly_current_size澈缺,每次aof發(fā)生變化都會維護(hù)這個字段坪创。
aof.c
=================
116 nwritten = write(server.appendfd,server.aofbuf姐赡,sdslen(server.aofbuf));
.....
128 server.appendonly_current_size += nwritten;
bgrewriteaof完畢或者實(shí)例啟動載入aof數(shù)據(jù)后也會調(diào)用aofUpdateCurrentSize這個函數(shù)維護(hù)這個字段莱预,同時會記錄下此時的aof文件的大小server.auto_aofrewrite_base_size作為基準(zhǔn)值,用于接下來判斷aof增長率项滑。
aof.c
=================
385 aofUpdateCurrentSize();
386 server.auto_aofrewrite_base_size = server.appendonly_current_size;
有了當(dāng)前值和基準(zhǔn)值我們就可以判斷aof文件的增長情況依沮。另外還需要配置兩個參數(shù)來判斷是否需要自動觸發(fā)bgrewriteaof。
redis.h
===============
int auto_aofrewrite_perc; /* Rewrite AOF if % growth is > M and... */
off_t auto_aofrewrite_min_size; /* the AOF file is at least N bytes. */
auto_aofrewrite_perc: aof文件的大小超過基準(zhǔn)百分之多少后觸發(fā)bgrewriteaof。默認(rèn)這個值設(shè)置為100危喉,意味著當(dāng)前aof是基準(zhǔn)大小的兩倍的時候觸發(fā)bgrewriteaof宋渔。把它設(shè)置為0可以禁用自動觸發(fā)的功能。
auto_aofrewrite_min_size: 當(dāng)前aof文件大于多少字節(jié)后才觸發(fā)辜限。避免在aof較小的時候無謂行為傻谁。默認(rèn)大小為64mb。
兩個參數(shù)都是可以在conf里靜態(tài)配置列粪,或者通過config set來動態(tài)修改的。
redis 127.0.0.1:6379> config get auto-aof-rewrite-percentage
1) "auto-aof-rewrite-percentage"
2) "100"
redis 127.0.0.1:6379> config get auto-aof-rewrite-min-size
1) "auto-aof-rewrite-min-size"
2) "1048576"
redis 127.0.0.1:6379> config get auto-aof-rewrite-min-size
1) "auto-aof-rewrite-min-size"
2) "1048576"
redis 127.0.0.1:6379> config set auto-aof-rewrite-percentage 200
OK
redis 127.0.0.1:6379> config set auto-aof-rewrite-min-size 10485760
OK
然后就是觸發(fā)檢查的主邏輯谈飒,serverCron時間事件中每次都會檢查現(xiàn)有狀態(tài)和參數(shù)來判斷是否需要啟動bgrewriteaof岂座。
redis.c
===============
635 if (server.bgsavechildpid == -1 &&
636 server.bgrewritechildpid == -1 &&
637 server.auto_aofrewrite_perc &&
638 server.appendonly_current_size > server.auto_aofrewrite_min_size)
639 {
640 long long base = server.auto_aofrewrite_base_size ?
641 server.auto_aofrewrite_base_size : 1;
642 long long growth = (server.appendonly_current_size*100/base) - 100;
643 if (growth >= server.auto_aofrewrite_perc) {
644 redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth"杭措,growth);
645 rewriteAppendOnlyFileBackground();
646 }
647 }
以上代碼顯示费什,如果aof文件增長百分率growth大于auto_aofrewrite_perc,則自動的觸發(fā)后一個bgrewriteaof手素。
- 延遲bgrewriteaof
這是個小的改進(jìn)鸳址,手動觸發(fā)的bgrewriteaof的時候如果同時存在bgsave在備份,會推遲這次操走的事件泉懦,設(shè)置server.aofrewrite_scheduled=1稿黍,待到bgsave結(jié)束后的下一次serverCron里才會觸發(fā)。
設(shè)置aofrewrite_scheduled=1
aof.c
706 void bgrewriteaofCommand(redisClient *c) {
707 if (server.bgrewritechildpid != -1) {
708 addReplyError(c崩哩,"Background append only file rewriting already in progress");
709 } else if (server.bgsavechildpid != -1) {
710 server.aofrewrite_scheduled = 1;
711 addReplyStatus(c巡球,"Background append only file rewriting scheduled");
712 } else if (rewriteAppendOnlyFileBackground() == REDIS_OK) {
713 addReplyStatus(c,"Background append only file rewriting started");
714 } else {
715 addReply(c邓嘹,shared.err);
716 }
717 }
- 觸發(fā)bgrewriteaof
redis.c
598 if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1 &&
599 server.aofrewrite_scheduled)
600 {
601 rewriteAppendOnlyFileBackground();
602 }
總結(jié):
rewrite機(jī)制:aof里存放了所有的redis 操作指令酣栈,當(dāng)aof文件達(dá)到一定條件或者手動
bgrewriteaof命令都可以觸發(fā)rewrite。
rewrite之后aof文件會保存keys的最后的狀態(tài)汹押,清除掉之前冗余的矿筝,來縮小這個文件。
- 自動觸發(fā)的條件:
long long growth =(server.appendonly_current_size*100/base) - 100;
if (growth >=server.auto_aofrewrite_perc)
在配置文件里設(shè)置過:
auto-aof-rewrite-percentage 100 (當(dāng)前寫入日志文件的大小超過上一次rewrite之后的文件大小的百分之100時就是2倍時觸發(fā)Rewrite)