上一篇文章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相關學習資料