上一篇文章Redis持久化——內存快照(RDB)我們總結到使用Redis
內存快照進行持久化,在t 時刻做了一次快照,然後又在 t+n 時刻做了一次快照,此時如果宕機,則會丟失在此期間內修改的數據。但又不能頻繁的進行內存快照,那麼有什麼辦法能夠儘可能的減少這種數據丟失呢?Redis
提供了另一種持久化的方式——AOF
日誌(Append Only File)。
什麼是AOF
日誌持久化
執行後寫日誌
與內存快照保存當前內存中的數據所不同,AOF
持久化是通過保存Redis
服務器所執行的寫命令來記錄數據庫狀態的。即每執行一個命令,就會把該命令寫到日誌文件裏。
需要注意的是寫日誌的操作在Redis
執行命令將數據寫入內存之後,如下圖所示:
這樣做的好處就是不會阻塞當前操作,也可以避免額外的檢查開銷,如果是在命令執行前進行寫日誌的操作,一旦命令語法是錯誤的,不進行檢查的話就會導致寫入到日誌文件中的命令是錯誤的,在使用日誌文件恢復數據的時候就會出錯。而在命令執行後在進行日誌的寫入則不會有這個問題。
但是也存在兩個問題,
-
AOF
雖然避免了對當前命令的阻塞,但卻可能會給下一個操作帶來阻塞風險。因爲,AOF
日誌是在主進程中執行的,如果在把日誌文件寫入磁盤時,磁盤寫壓力大,就會導致寫盤很慢,進而導致後續的操作也無法執行了 -
如果剛執行完一個命令,還沒有來得及記日誌就宕機了,那麼這個命令和相應的數據就有丟失的風險。如果此時
Redis
是用作緩存,還可以從後端數據庫重新讀入數據進行恢復,但是,如果Redis
是直接用作數據庫的話,此時,因爲命令沒有記入日誌,所以就無法用日誌進行恢復了。
AOF
緩衝區
針對上面兩個問題,Redis
提供了緩衝區的方式進行AOF
日誌的記錄,以達到儘可能的避免阻塞和數據丟失的問題。
即Redis
在執行完命令進行持久化的時候,並非直接寫入磁盤日誌文件,而是先寫入AOF
緩衝區內,之後再通過某種策略寫到磁盤。
使用緩存區的方式進行AOF
日誌的記錄,上面提到的兩個問題其實就和日誌從緩衝區寫入磁盤的時機有關係。
三種回寫策略
Redis AOF
機制提供了三種回寫磁盤的策略。
Always(同步寫回)
: 命令寫入AOF
緩衝區後調用系統fsync
操作同步到AOF
文件,fsync
完成後線程返回Everysec(每秒寫回)
: 命令寫人AOF
緩衝區後調用系統write
操作,write
完成後線程返回。fsync
同步文件操作由專門線程每秒調用一次No(操作系統自動寫回)
: 命令寫入AOF
緩衝區後調用系統write
操作,不對AOF
文件做fsync
同步,同步硬盤操作由操作系統負責,通常同步週期最長30秒
但其實可以看出這三種回寫策略都並不能完美的解決問題,
配置爲 always
時,每次寫入都要同步AOF
文件,硬盤的寫入速度無法與內存相提並論,顯然與 Redis
髙性能特性背道而馳
配置爲no
,由於操作系統每次同步AOF
文件的週期不可控,而且會加大每次同步硬盤的數據量,雖然提升了性能,但數據安全性無法保證。
配置爲 everysec
,是建議的同步策略,也是默認配置,雖然能做到兼顧性能和數據安全性。但極端情況下一會造成1秒內的數據丟失。
在真正使用中,我們可以根據具體對性能和數據完整性的要求,分析這三種回寫策略,選擇適合的策略來進行持久化。
回寫策略 | 優點 | 缺點 |
---|---|---|
Always(同步寫回) | 可靠性高、數據基本不丟失 | 性能較差 |
Everysec(每秒寫回) | 性能適中 | 宕機時丟失1秒內的數據 |
No(操作系統自動寫回) | 性能好 | 宕機時丟失數據較多 |
AOF
重寫
日誌文件越來越大怎麼辦
選擇了合適的回寫策略,AOF
這種持久化的方式還有其它問題嗎?
因爲AOF
持久化是通過保存被執行的寫命令來記錄數據庫狀態的,所以隨着時間的流逝,AOF
文件中的內容會越來越多,文件的體積也會越來越大,過大的AOF
文件不僅追加命令會變慢,而且可能對Redis
服務器、甚至整個宿主計算機造成影響,並且AOF
文件的體積越大,使用AOF
文件來進行數據還原所需的時間就越多。
這個時候就要用到AOF
重寫機制了
redis> set testKey testValue
OK
redis> set testKey testValue1
OK
redis> del testKey
OK
redis> set testKey hello
OK
redis> set testKey world
OK
AOF
文件是以追加的方式,逐一記錄接收到的寫命令的。當一個鍵值對被多條寫命令反覆修改時,AOF
文件會記錄相應的多條命令。如上示例,我們執行完命令後,Redis
會在AOF裏面追加5條命令。但實際上只需要set testKey world
一條命令就夠了。
AOF
重寫機制就是在重寫時,Redis
根據數據庫的現狀創建一個新的 AOF
文件,也就是說,讀取數據庫中的所有鍵值對,然後對每一個鍵值對用一條命令記錄它的寫入。比如說,當讀取了鍵值對“testkey”: “world”
之後,重寫機制會記錄 set testkey world
這條命令。這樣,當需要恢復時,可以重新執行該命令,實現“testkey”: “world”
的寫入。
這樣,重寫後的日誌,從5條變成了1條,而對於可能被修改過成百上千次的鍵值對來說,重寫能節省的空間就更大了。
雖然 AOF
重寫後,日誌文件會縮小,但是,要把整個數據庫的最新數據的操作日誌都寫回磁盤,仍然是一個非常耗時的過程。這時,我們不得不關注:重寫會不會導致阻塞?這就要看看AOF
重寫的過程是怎麼樣的
AOF
重寫過程
因爲AOF
重寫也是一個非常耗時的過程,又因爲Redis
單線程的特性,同內存快照一樣,AOF
重寫的過程也是由父進程fork出bgrewriteaof
子進程來完成的.
使用子進程(而不是開啓一個線程)進行AOF重寫雖然可以避免使用鎖的情況下,保證數據安全性,但是會帶來子進程和父進程一致性問題。
例如在開始重寫之後父進程又接收了新的鍵值對此時子進程是無法知曉的,當子進程重寫完成後的數據庫和父進程的數據庫狀態是不一致的。
如下表:
時間 | 服務器進程(父進程) | 子進程 |
---|---|---|
T1 | 執行命令 SET K1 V1 | |
T2 | 執行命令 SET K1 V1 | |
T3 | 創建子進程,執行AOF文件重寫 | 開始AOF重寫 |
T4 | 執行命令 SET K2 V2 | 執行重寫 |
T5 | 執行命令 SET K3 V3 | 執行重寫 |
T6 | 執行命令 SET K4 V4 | 完成AOF重寫 |
在T6時刻服務器進程有了4個鍵,而子進程卻只有1個鍵
爲了解決這種不一致性,Redis
設置了一個AOF
重寫緩衝區。
在子進程執行AOF
重寫期間。服務器進程需要執行以下3個動作:
- 執行客戶端命令
- 執行後追加到
AOF
緩衝區 - 執行後追加到
AOF
重寫緩衝區
子進程完成AOF
重寫後,它向父進程發送一個信號,父進程收到信號後會調用一個信號處理函數,該函數把AOF
重寫緩衝區的命令追加到新AOF
文件中然後替換掉現有AOF
文件。父進程處理完畢後可以繼續接受客戶端命令調用,可以看出在AOF
後臺重寫過程中只有這個信號處理函數會阻塞服務器進程。
下表是完整的AOF
後臺重寫過程:
時間 | 服務器進程(父進程) | 子進程 |
---|---|---|
T1 | 執行命令 SET K1 V1 | |
T2 | 執行命令 SET K1 V1 | |
T3 | 創建子進程,執行AOF文件重寫 | 開始AOF重寫 |
T4 | 執行命令 SET K2 V2 | 執行重寫 |
T5 | 執行命令 SET K3 V3 | 執行重寫 |
T6 | 執行命令 SET K4 V4 | 完成AOF重寫,向父進程發送信號 |
T7 | 接收到信號,將T5 T6 T7 服務器的寫命令追加到新的AOF文件末尾 | |
T8 | 用新的AOF替換舊的AOF |
這樣就可以保證重寫日誌期間的所有操作也都會寫入新的AOF文件。
需要注意的是, T7 T8執行的任務會阻塞服務器處理命令。
總的來說,就是每次 AOF
重寫時,Redis
會先fork出一個子進程用於重寫;然後,使用兩個日誌保證在重寫過程中,新寫入的數據不會丟失。
AOF
文件恢復
在Redis
服務器重啓後,會優先去載入AOF
日誌文件。因爲AOF
文件裏面包含了重建數據庫狀態所需的所有寫命令,所以服務器重新執行一遍AOF
文件裏面保存的寫命令,就可以還原服務器關閉之前的數據庫狀態。
而由於Redis
命令只能在客戶端上下文中執行,Redis
會創建一個沒有網絡連接的僞客戶端來執行AOF
文件中的內容。
小結
本文主要總結了Redis AOF
持久化的方式,介紹了它同步磁盤的三種策略,以及日誌文件過大時如何進行重寫。我們知道Redis
持久化方式有AOF和RDB兩種,那麼這兩種持久化方式各自有什麼優點和缺點?真正使用中我們應該如何去選擇合適的持久化方式,又可能遇到哪些問題呢?我們下一篇文章繼續總結
系列文章:
-----END-----
關注下方公衆號,回覆“Redis”,可得Redis相關學習資料