Redis是記憶體資料庫,但是一旦伺服器當機,記憶體中的資料將會全部丟失。
最簡單的恢復方式是從後端資料庫恢復,但這種方式有兩個問題:
- 頻繁訪問資料庫,會給資料庫帶來巨大的壓力;
- 從資料庫中讀取相比從Redis中讀取要慢很多,會導致應用響應變慢
因此,Redis要實現持久化,避免從後端資料庫中進行恢復。
Redis有兩種持久化機制:AOF(Append Only File)日誌和RDB快照。今天先來學習AOF日誌。
什麼是AOF日誌?
AOF日誌是通過儲存Redis寫命令來記錄資料庫資料的。大多數的資料庫採用的是寫前日誌(WAL),例如MySQL,通過寫前日誌和兩階段提交,實現資料和邏輯的一致性。想了解更多關於兩階段提交的內容,點選檢視。
而AOF日誌採用寫後日志,即先寫記憶體,後寫日誌。
為什麼採用寫後日志?
Redis要求高效能,採用寫日誌有兩方面好處:
- 避免額外的檢查開銷
- 不會阻塞當前的寫操作
但這種方式存在潛在風險:
- 如果命令執行完成,寫日誌之前當機了,會丟失資料。
- 主執行緒寫磁碟壓力大,導致寫盤慢,阻塞後續操作。
如何實現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
函式,將一些資料寫入檔案時,作業系統通常會將資料暫存到一個記憶體緩衝區裡,當緩衝區的空間被填滿或超過了指定時限後,才真正將緩衝區的資料寫入到磁碟裡。這樣的操作雖然提高了效率,但也為資料寫入帶來了安全問題:如果計算機停機,記憶體緩衝區中的資料會丟失。為此,系統提供了
fsync
、fdatasync
同步函式,可以強制作業系統立刻將緩衝區中的資料寫入到硬碟裡,從而確保寫入資料的安全性。
如何配置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重寫過程不會阻塞主執行緒,因為是通過後臺bgrewriteaof執行緒來執行的。
但是在fork子程式的時候,fork這個瞬間一定是會阻塞主執行緒的。
fork採用的是作業系統提供的寫時複製(Copy On Write)機制,避免一次性拷貝造成的阻塞。但fork子程式需要拷貝程式必要的資料結構,其中有一項是拷貝記憶體頁表(虛擬記憶體和實體記憶體的對映索引表),這個拷貝過程會消耗大量的CPU資源,在拷貝完成之前,整個程式是會阻塞的。
拷貝記憶體頁完成後,子程式與父程式指向相同的記憶體地址空間,也就是說此時雖然產生了子程式,但是並沒有申請與父程式相同的記憶體大小。
那什麼時候父子程式才會真正記憶體分離呢?在寫發生時,才真正拷貝記憶體的資料,這個過程中,父程式也可能會產生阻塞風險。
因為記憶體分配是以頁為單位進行分配的,預設4K,如果父程式此時操作的是一個bigkey,重新申請大塊記憶體耗時會變長,可能會產生阻塞風險。
另外,如果作業系統開啟了記憶體大頁機制(Huge Page,頁面大小2M),那麼父程式申請記憶體時阻塞的概率將會大大提高,所以在Redis機器上需要關閉Huge Page機制。
為什麼AOF重寫不復用原AOF日誌
有兩方面原因:
- 父子程式寫同一個檔案會產生競爭問題,影響父程式的效能。
- 如果AOF重寫過程中失敗了,相當於汙染了原本的AOF檔案,無法做恢復資料使用。
AOF重寫需要手動觸發嗎?
可以設定自動觸發,通過配置這兩個引數auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
:
auto-aof-rewrite-min-size
:表示執行AOF重寫時檔案的最小大小,預設為64MBauto-aof-rewrite-percentage
:當前AOF檔案大小和上一次重寫後AOF檔案大小的差值,再除以上一次重寫後AOF檔案大小
當AOF檔案大小同時超出上面兩個配置項,會觸發AOF重寫。