Redis核心解讀–AOF與REWRITE機制


Redis AOF 簡介

Redis AOF是類似於log的機制,每次寫操作都會寫到硬盤上,當系統崩潰時,可以通過AOF來恢復數據。每個帶有寫操作的命令被Redis服務器端收到運行時,該命令都會被記錄到AOF文件上。由於只是一個append到文件操作,所以寫到硬盤上的操作往往非常快。

其實Redis oaf機制包括了兩件事,rewrite和AOF。rewrite類似於普通數據庫管理系統日志恢復點,當AOF文件隨着寫命令的運行膨脹時,當文件大小觸碰到臨界時,rewrite會被運行。
rewrite會像replication一樣,fork出一個子進程,創建一個臨時文件,遍歷數據庫,將每個key、value對輸出到臨時文件。輸出格式就是Redis的命令,但是為了減小文件大小,會將多個key、value對集合起來用一條命令表達。在rewrite期間的寫操作會保存在內存的rewrite buffer中,rewrite成功后這些操作也會復制到臨時文件中,在最后臨時文件會代替AOF文件。
以上在AOF打開的情況下,如果AOF是關閉的,那么rewrite操作可以通過bgrewriteaof命令來進行。

Redis AOF流程

  1. Redis Server啟動,如果AOF機制打開那么初始化AOF狀態,並且如果存在AOF文件,讀取AOF文件。
  2. 隨着Redis不斷接受命令,每個寫命令都被添加到AOF文件,AOF文件膨脹到需要rewrite時又或者接收到客戶端的bgrewriteaof命令。
  3. fork出一個子進程進行rewrite,而父進程繼續接受命令,現在的寫操作命令都會被額外添加到一個aof_rewrite_buf_blocks緩沖中。
  4. 當子進程rewrite結束后,父進程收到子進程退出信號,把aof_rewrite_buf_blocks的緩沖添加到rewrite后的文件中,然后切換AOF的文件fd。rewrite任務完成,繼續第二個步驟。

關鍵點

    • 由於寫操作通常是有緩沖的,所以有可能AOF操作並沒有寫到硬盤中,一般可以通過fsync()來強制輸出到硬盤中。而fsync()的頻率可以通過配置文件中的flush策略來指定,可以選擇每次事件循環寫操作都強制fsync或者每秒fsync至少運行一次。
    • 當rewrite子進程開始后,父進程接受到的命令會添加到aof_rewrite_buf_blocks中,使得rewrite成功后,將這些命令添加到新文件中。在rewrite過程中,原來的AOF也可以選擇是不是繼續添加,由於存在性能上的問題,在rewrite過程中,如果fsync()繼續執行,會導致IO性能受損影響Redis性能。所以一般情況下rewrite期間禁止fsync()到舊AOF文件。這策略可以在配置文件中修改。
    • 在rewrite結束后,在將新rewrite文件重命名為配置中指定的文件時,如果舊AOF存在,那么會unlink掉舊文件。這是就存在一個問題,處理rewrite文件遷移的是主線程,rename(oldpath, newpath)過程會覆蓋舊文件,這是rename會unlink(oldfd),而unlink操作會導致block主線程。這時,我們就需要類似libeio(http://software.schmorp.de/pkg/libeio.html)這樣的庫去進行異步的底層IO。作者在bio.c有一個類似的機制,通過創建新線程來進行異步操作。
    • ==========================================================================================================
    • 自動的bgrewriteaof

      為了避免aof文件過大,我們會周期性的做bgrewriteaof來重整aof文件。以前我們會額外的配置crontab在業務低峰期執行這個命令,這額外的增加一個workaroud的腳本任務在大集群里是很糟糕的,不易檢查,出錯無法即時發現。

      於是這個自動bgrewriteaof功能被直接加到redis的內部。首先對於aof文件,server對象添加一個字段來記錄aof文件的大小server.appendonly_current_size,每次aof發生變化都會維護這個字段。

      aof.c
      =================
      116     nwritten = write(server.appendfd,server.aofbuf,sdslen(server.aofbuf));
      .....
      128     server.appendonly_current_size += nwritten;

      bgrewriteaof完畢或者實例啟動載入aof數據后也會調用aofUpdateCurrentSize這個函數維護這個字段,同時會記錄下此時的aof文件的大小server.auto_aofrewrite_base_size作為基准值,用於接下來判斷aof增長率。

      aof.c
      =================
      385     aofUpdateCurrentSize();
      386     server.auto_aofrewrite_base_size = server.appendonly_current_size;

      有了當前值和基准值我們就可以判斷aof文件的增長情況。另外還需要配置兩個參數來判斷是否需要自動觸發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文件的大小超過基准百分之多少后觸發bgrewriteaof。默認這個值設置為100,意味着當前aof是基准大小的兩倍的時候觸發bgrewriteaof。把它設置為0可以禁用自動觸發的功能。
      auto_aofrewrite_min_size: 當前aof文件大於多少字節后才觸發。避免在aof較小的時候無謂行為。默認大小為64mb。
      兩個參數都是可以在conf里靜態配置,或者通過config set來動態修改的。

      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

      然后就是觸發檢查的主邏輯,serverCron時間事件中每次都會檢查現有狀態和參數來判斷是否需要啟動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,則自動的觸發后一個bgrewriteaof。

      延遲bgrewriteaof

      這是個小的改進,手動觸發的bgrewriteaof的時候如果同時存在bgsave在備份,會推遲這次操走的事件,設置server.aofrewrite_scheduled=1,待到bgsave結束后的下一次serverCron里才會觸發。

      設置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 }

      觸發bgrewriteaof

      redis.c
       598     if (server.bgsavechildpid == -1 && server.bgrewritechildpid == -1 &&
       599         server.aofrewrite_scheduled)
       600     {
       601         rewriteAppendOnlyFileBackground();
       602    }

      總結:

      rewrite機制:aof里存放了所有的redis 操作指令,當aof文件達到一定條件或者手動

      bgrewriteaof命令都可以觸發rewrite

      rewrite之后aof文件會保存keys的最后的狀態,清除掉之前冗余的,來縮小這個文件。

      自動觸發的條件:

       long long growth =(server.appendonly_current_size*100/base) - 100;
       if (growth >=server.auto_aofrewrite_perc)

       

      在配置文件里設置過:

      auto-aof-rewrite-percentage 100 (當前寫入日志文件的大小超過上一次rewrite之后的文件大小的百分100就是2倍時觸發Rewrite


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM