天天看點

Redis · 特性分析 · AOF Rewrite 分析

redis提供兩種持久化機制

rdb: 将資料庫的快照以二進制的方式儲存到磁盤;

aof: 将所有寫入指令及相關參數以協定文本的方式寫入檔案并持久儲存磁盤。

本文隻關心aof,簡單介紹一下:redis server将所有寫入的指令轉換成協定文本的方式寫入aof檔案,例如:server收到 set key value的的寫入指令,server會進行以下幾步操作:

将指令轉換成協定文本,轉換後的結果:<code>*3\r\n$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n</code>;

将協定文本追加到aof緩存,也就是aof_buf;

根據sync政策調用fsync/fdatasync。

到目前為止已經成功儲存資料,如果想要還原aof,隻需要将aof裡指令讀出來并重放就可以還原資料庫。

aof持久化機制存在一個緻命的問題,随着時間推移,aof檔案會膨脹,如果頻繁寫入aof檔案會膨脹到無限大,當server重新開機時嚴重影響資料庫還原時間,影響系統可用性。為解決此問題,系統需要定期重寫aof檔案,目前采用的方式是建立一個新的aof檔案,将資料庫裡的全部資料轉換成協定的方式儲存到檔案中,通過此操作達到減少aof檔案大小的目的,重寫後的大小一定是小于等于舊aof檔案的大小。

重寫aof提供兩種方式

rewrite: 在主線程中重寫aof,會阻塞工作線程,在生産環境中很少使用,處于廢棄狀态;

bgrewrite: 在背景(子程序)重寫aof, 不會阻塞工作線程,能正常服務,此方法最常用。

本文隻關心bgrewrite的問題,是以隻介紹此指令的實作機制。

server收到bgrewrite指令或者系統觸發aof重寫時,主進建立一個子程序并進行aof重寫,主程序異步等待子程序結束(信号量),此時主程序能正常接收處理使用者請求,使用者請求會修改資料庫裡資料,會使得目前資料庫的資料跟重寫後aof裡不一緻,需要有種機制保證資料的一緻性。目前的做法是在重寫 aof 期間系統會新開一塊記憶體用于緩存重寫期間收到的指令,在重寫完成以後再将緩存中的資料追加到新的aof。在處理指令時既要将指令追加到 aof_buf,也要追加到重寫aof buffer。

重寫aof buffer是個不限大小的buffer,但使用者寫入的資料量較多時會出現以下兩個問題:

占用過多記憶體,浪費資源;

主程序将aof buffer資料寫入到新aof檔案中時會阻塞工作線程,使用者正常請求的延時會變高,嚴重情況下會逾時,主備同步也會出問題,斷開重連,重新同步等。

主要思路是aof重寫期間,主程序跟子程序通過管道通信,主程序實時将新寫入的資料發送給子程序,子程序從管道讀出資料交緩存在buffer中,子程序等待存量資料全部寫入aof檔案後,将緩存資料追加到aof檔案中,此方案隻是解決阻塞工作線程問題,但占用記憶體過多問題并沒有解決。

主要思路是aof重寫期間,主程序建立一個新的aof_buf,新的aof檔案用于接收新寫入的指令,sync政策保持不變,在aof重寫期間,系統需要向兩個aof_buf,兩個aof檔案同時追加新寫入的指令。當主程序收到子程序重寫aof檔案完成後,停止向老的aof_buf,aof檔案追加指令,然後删除舊的aof檔案(流程跟原來保持一緻);将将子程序新生成的aof檔案重命名為appendonly.aof.last,具體流程如下:

停止向舊的aof_buf,aof檔案追加指令;

删除舊的的appendonly.aof.last檔案;

交換兩個aof_buf,aof檔案指針;

回收舊的aof_buf,aof檔案;

重指令子程序生成的aof檔案為appendonly.aof.last;

系統運作期間同時存在兩個aof檔案,一個是目前正在寫的aof,另一個是存量的aof資料檔案。是以需要修改資料庫恢複相關邏輯,加載aof時先要加載存量資料appendonly.aof.last,再加載appendonly.aof。