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