Redis基礎篇(三)持久化:AOF日誌

大雜草發表於2020-12-28

Redis是記憶體資料庫,但是一旦伺服器當機,記憶體中的資料將會全部丟失。

最簡單的恢復方式是從後端資料庫恢復,但這種方式有兩個問題:

  1. 頻繁訪問資料庫,會給資料庫帶來巨大的壓力;
  2. 從資料庫中讀取相比從Redis中讀取要慢很多,會導致應用響應變慢

因此,Redis要實現持久化,避免從後端資料庫中進行恢復。

Redis有兩種持久化機制:AOF(Append Only File)日誌和RDB快照。今天先來學習AOF日誌。

什麼是AOF日誌?

AOF日誌是通過儲存Redis寫命令來記錄資料庫資料的。大多數的資料庫採用的是寫前日誌(WAL),例如MySQL,通過寫前日誌兩階段提交,實現資料和邏輯的一致性。想了解更多關於兩階段提交的內容,點選檢視

而AOF日誌採用寫後日志,即先寫記憶體,後寫日誌。

為什麼採用寫後日志?

Redis要求高效能,採用寫日誌有兩方面好處:

  1. 避免額外的檢查開銷
  2. 不會阻塞當前的寫操作

但這種方式存在潛在風險:

  1. 如果命令執行完成,寫日誌之前當機了,會丟失資料。
  2. 主執行緒寫磁碟壓力大,導致寫盤慢,阻塞後續操作。

如何實現AOF日誌?

AOF日誌記錄Redis的每個寫命令,步驟分為:命令追加(append)、檔案寫入(write)和檔案同步(sync)。

命令追加

當AOF持久化功能開啟了,伺服器在執行完一個寫命令之後,會以協議格式將被執行的寫命令追加到伺服器的 aof_buf 緩衝區。

檔案寫入和同步

關於何時將 aof_buf 緩衝區的內容寫入AOF檔案中,Redis提供了三種寫回策略:

  • Always,同步寫回:每個寫命令執行完,立馬同步地將日誌寫回磁碟;
  • Everysec,每秒寫回:每個寫命令執行完,只是先把日誌寫到AOF檔案的記憶體緩衝區,每隔一秒把緩衝區中的內容寫入磁碟;
  • No,作業系統控制的寫回:每個寫命令執行完,只是先把日誌寫到AOF檔案的記憶體緩衝區,由作業系統決定何時將緩衝區內容寫回磁碟。

三種寫回策略的優缺點

上面的三種寫回策略體現了一個重要原則:trade-off,取捨,指在效能和可靠性保證之間做取捨

關於AOF的同步策略是涉及到作業系統的 write 函式和 fsync 函式的,在《Redis設計與實現》中是這樣說明的:

為了提高檔案寫入效率,在現代作業系統中,當使用者呼叫write函式,將一些資料寫入檔案時,作業系統通常會將資料暫存到一個記憶體緩衝區裡,當緩衝區的空間被填滿或超過了指定時限後,才真正將緩衝區的資料寫入到磁碟裡。

這樣的操作雖然提高了效率,但也為資料寫入帶來了安全問題:如果計算機停機,記憶體緩衝區中的資料會丟失。為此,系統提供了fsyncfdatasync同步函式,可以強制作業系統立刻將緩衝區中的資料寫入到硬碟裡,從而確保寫入資料的安全性。

如何配置AOF?

預設情況下,Redis是沒有開啟AOF的,可以通過配置redis.conf檔案來開啟AOF持久化,關於AOF的配置如下:

# appendonly引數開啟AOF持久化
appendonly no

# AOF持久化的檔名,預設是appendonly.aof
appendfilename "appendonly.aof"

# AOF檔案的儲存位置和RDB檔案的位置相同,都是通過dir引數設定的
dir ./

# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重寫期間是否同步
no-appendfsync-on-rewrite no

# 重寫觸發配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 載入aof出錯如何處理
aof-load-truncated yes

# 檔案重寫策略
aof-rewrite-incremental-fsync yes

AOF重寫

AOF會記錄每個寫命令到AOF檔案,隨著時間越來越長,AOF檔案會變得越來越大。如果不加以控制,會對Redis伺服器,甚至對作業系統造成影響,而且AOF檔案越大,資料恢復也越慢。

為了解決AOF檔案體積膨脹的問題,Redis提供AOF檔案重寫功能來對AOF檔案進行“瘦身”。Redis通過建立一個新的AOF檔案來替換現有的AOF,新舊兩個AOF檔案儲存的資料相同,但新AOF檔案沒有了冗餘命令。

簡單來說,AOF重寫就是把舊AOF日誌檔案的多條命令,在重寫後變成新日誌檔案的一條命令。

AOF重寫會阻塞嗎?AOF重寫是由後臺執行緒bgrewriteaof來完成的。

AOF重寫過程

用一句話總結:一個拷貝,兩處日誌。一個拷貝指一份記憶體拷貝,兩處日誌分別是一處正在使用的AOF日誌,另一處是新的AOF重寫日誌。

下圖是AOF重寫過程:

AOF重寫過程

擴充

關於AOF重寫過程的潛在阻塞風險

前面提到AOF重寫不會阻塞,指的是在AOF重寫過程不會阻塞主執行緒,因為是通過後臺bgrewriteaof執行緒來執行的。

但是在fork子程式的時候,fork這個瞬間一定是會阻塞主執行緒的。

fork採用的是作業系統提供的寫時複製(Copy On Write)機制,避免一次性拷貝造成的阻塞。但fork子程式需要拷貝程式必要的資料結構,其中有一項是拷貝記憶體頁表(虛擬記憶體和實體記憶體的對映索引表),這個拷貝過程會消耗大量的CPU資源,在拷貝完成之前,整個程式是會阻塞的。

拷貝記憶體頁完成後,子程式與父程式指向相同的記憶體地址空間,也就是說此時雖然產生了子程式,但是並沒有申請與父程式相同的記憶體大小。

那什麼時候父子程式才會真正記憶體分離呢?在寫發生時,才真正拷貝記憶體的資料,這個過程中,父程式也可能會產生阻塞風險。

因為記憶體分配是以頁為單位進行分配的,預設4K,如果父程式此時操作的是一個bigkey,重新申請大塊記憶體耗時會變長,可能會產生阻塞風險。

另外,如果作業系統開啟了記憶體大頁機制(Huge Page,頁面大小2M),那麼父程式申請記憶體時阻塞的概率將會大大提高,所以在Redis機器上需要關閉Huge Page機制。

為什麼AOF重寫不復用原AOF日誌

有兩方面原因:

  1. 父子程式寫同一個檔案會產生競爭問題,影響父程式的效能。
  2. 如果AOF重寫過程中失敗了,相當於汙染了原本的AOF檔案,無法做恢復資料使用。

AOF重寫需要手動觸發嗎?

可以設定自動觸發,通過配置這兩個引數auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage

  • auto-aof-rewrite-min-size:表示執行AOF重寫時檔案的最小大小,預設為64MB
  • auto-aof-rewrite-percentage:當前AOF檔案大小和上一次重寫後AOF檔案大小的差值,再除以上一次重寫後AOF檔案大小

當AOF檔案大小同時超出上面兩個配置項,會觸發AOF重寫。

參考資料

相關文章