詳解Redis的持久化機制--RDB和AOF
redis跟memcached類似,都是內存數據庫,不過redis支持數據持久化,也就是說redis可以將內存中的數據同步到磁盤來持久化,以確保redis 的數據安全。不過持久化這塊可能比較容易產生誤解,下面聊聊這塊。
Redis持久化是如何工作的?
什么是持久化?簡單來講就是將數據放到斷電后數據不會丟失的設備中,也就是我們通常理解的硬盤上。
1. 數據庫寫操作的5個過程
首先我們來看一下數據庫在進行寫操作時到底做了哪些事,主要有下面五個過程:
- 客戶端向服務端發送寫操作(數據在客戶端的內存中)。
- 數據庫服務端接收到寫請求的數據(數據在服務端的內存中)。
- 服務端調用write這個系統調用,將數據往磁盤上寫(數據在系統內存的緩沖區中)。
- 操作系統將緩沖區中的數據轉移到磁盤控制器上(數據在磁盤緩存中)。
- 磁盤控制器將數據寫到磁盤的物理介質中(數據真正落到磁盤上)。
2. 故障分析
寫操作大致有上面5個流程,當數據庫系統故障時,這時候系統內核還是完好的。那么此時只要我們執行完了第3步,那么數據就是安全的,因為后續操作系統會來完成后面幾步,保證數據最終會落到磁盤上。當系統斷電時,這時候上面5項中提到的所有緩存都會失效,并且數據庫和操作系統都會停止工作。所以只有當數據在完成第5步后,才能保證在斷電后數據不丟失。
【補充】這里可能有幾個疑問:
- 數據庫多長時間調用一次write,將數據寫到內核緩沖區?
- 內核多長時間會將系統緩沖區中的數據寫到磁盤控制器?
- 磁盤控制器又在什么時候把緩存中的數據寫到物理介質上?
對于***個問題,通常數據庫層面會進行全面控制。
而對第二個問題,操作系統有其默認的策略,但是我們也可以通過POSIX API提供的fsync系列命令強制操作系統將數據從內核區寫到磁盤控制器上。
對于第三個問題,看起來數據庫已經無法觸及,但實際上,大多數情況下磁盤緩存是被設置關閉的,或者是只開啟為讀緩存,也就是說寫操作不會進行緩存,直接寫到磁盤。建議的做法是僅僅當你的磁盤設備有備用電池時才開啟寫緩存。
3. 數據損壞
所謂數據損壞,就是數據無法恢復,上面我們講的都是如何保證數據是確實寫到磁盤上去,但是寫到磁盤上可能并不意味著數據不會損壞。比如我們可能一次寫請求會進行兩次不同的寫操作,當意外發生時,可能會導致一次寫操作安全完成,但是另一次還沒有進行。如果數據庫的數據文件結構組織不合理,可能就會導致數據完全不能恢復的狀況出現。
三種解決策略:
- 最粗糙的處理,就是不通過數據的組織形式保證數據的可恢復性。而是通過配置數據同步備份的方式,在數據文件損壞后通過數據備份來進行恢復。實際上MongoDB在不開啟操作日志,通過配置Replica Sets時就是這種情況。
- 在上面基礎上添加一個操作日志,每次操作時記一下操作的行為,這樣我們可以通過操作日志來進行數據恢復。因為操作日志是順序追加的方式寫的,所以不會出現操作日志也無法恢復的情況。這也類似于MongoDB開啟了操作日志的情況。
- 更保險的做法是數據庫不進行舊數據的修改,只是以追加方式去完成寫操作,這樣數據本身就是一份日志,這樣就永遠不會出現數據無法恢復的情況了。實際上CouchDB就是此做法的優秀范例。
那么,redis又針對持久化提供了什么方式呢?
redis持久化的兩種方式
redis提供了兩種持久化的方式,分別是RDB(Redis DataBase)和AOF(Append Only File)。
RDB,簡而言之,就是將存儲的數據快照的方式存儲到磁盤上,
AOF,則是將redis執行過的所有寫指令記錄下來,通過write函數追加到AOF文件的末尾。在下次redis重新啟動時,只要把這些寫指令從前到后再重復執行一遍,就可以實現數據恢復了。
RDB機制
1. 概念
RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。也是默認的持久化方式,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。
可以通過配置設置自動做快照持久化的方式。我們可以配置redis在n秒內如果超過m個key被修改就自動做快照,下面是默認的快照保存配置
- save 900 1 #900秒內如果超過1個key被修改,則發起快照保存
- save 300 10 #300秒內容如超過10個key被修改,則發起快照保存
- save 60 10000
2. RDB文件保存過程
- redis調用fork,現在有了子進程和父進程。
- 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。由于os的寫時復制機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會為父進程要修改的頁面創建副本,而不是寫共享的頁面。所以子進程的地址空間內的數 據是fork時刻整個數據庫的一個快照。
- 當子進程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進程退出。
client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主線程中保存快照的,由于redis是用一個主線程來處理所有 client的請求,這種方式會阻塞所有client請求。所以不推薦使用。
另一點需要注意的是,每次快照持久化都是將內存數據完整寫入到磁盤一次,并不是增量的只同步臟數據。如果數據量大的話,而且寫操作比較多,必然會引起大量的磁盤io操作,可能會嚴重影響性能。
3. 優勢
- 一旦采用該方式,那么你的整個Redis數據庫將只包含一個文件,這樣非常方便進行備份。比如你可能打算每1天歸檔一些數據。
- 方便備份,我們可以很容易的將一個一個RDB文件移動到其他的存儲介質上
- RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
- RDB 可以***化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然后這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。
4. 劣勢
- 如果你需要盡量避免在服務器故障時丟失數據,那么 RDB 不適合你。 雖然 Redis 允許你設置不同的保存點(save point)來控制保存 RDB 文件的頻率, 但是, 因為RDB 文件需要保存整個數據集的狀態, 所以它并不是一個輕松的操作。 因此可能會至少 5 分鐘才保存一次 RDB 文件。 在這種情況下, 一旦發生故障停機就可能會丟失好幾分鐘的數據。
- 每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,并由子進程來進行實際的持久化工作。 在數據集比較龐大時, fork() 可能會非常耗時,造成服務器在某某毫秒內停止處理客戶端; 如果數據集非常巨大,并且 CPU 時間非常緊張的話,那么這種停止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也需要進行 fork() ,但無論 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。
AOF
1. 概念
redis會將每一個收到的寫命令都通過write函數追加到文件中(默認是 appendonly.aof)。
當redis重啟時會通過重新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。當然由于os會在內核中緩存 write做的修改,所以可能不是立即寫到磁盤上。這樣aof方式的持久化也還是有可能會丟失部分修改。
可以通過配置文件告訴redis通過fsync函數強制os寫入到磁盤的時機。有三種方式如下(默認是:每秒fsync一次)
- appendonly yes //啟用aof持久化方式
- # appendfsync always //每次收到寫命令就立即強制寫入磁盤,最慢的,但是保證完全的持久化,不推薦使用
- appendfsync everysec //每秒鐘強制寫入磁盤一次,在性能和持久化方面做了很好的折中,推薦
- # appendfsync no //完全依賴os,性能***,持久化沒保證
2. AOF文件保存過程
aof 的方式也同時帶來了另一個問題。持久化文件會變的越來越大。例如我們調用incr test命令100次,文件中必須保存全部的100條命令,其實有99條都是多余的。因為要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。
為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照類似的方式將內存中的數據 以命令的方式保存到臨時文件中,***替換原來的文件。具體過程如下:
- redis調用fork ,現在有父子兩個進程
- 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
- 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證如果子進程重寫失敗的話并不會出問題。
- 當子進程把快照內容寫入已命令方式寫到臨時文件中后,子進程發信號通知父進程。然后父進程把緩存的寫命令也寫入到臨時文件。
- 現在父進程可以使用臨時文件替換老的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。
需要注意到是重寫aof文件的操作,并沒有讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點類似。
3. 優勢
- 使用 AOF 持久化會讓 Redis 變得非常耐久:你可以設置不同的 fsync 策略,比如無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。AOF 的默認策略為每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,并且就算發生故障停機,也最多只會丟失一秒鐘的數據( fsync 會在后臺線程執行,所以主線程可以繼續努力地處理命令請求)。
- AOF 文件是一個只進行追加操作的日志文件, 因此對 AOF 文件的寫入不需要進行 seek , 即使日志因為某些原因而包含了未寫入完整的命令(比如寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。
- Redis 可以在 AOF 文件體積變得過大時,自動地在后臺對 AOF 進行重寫: 重寫后的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操作是絕對安全的,因為 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件里面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,并開始對新 AOF 文件進行追加操作。
- AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕松。 導出(export) AOF 文件也非常簡單: 舉個例子, 如果你不小心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那么只要停止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重啟 Redis , 就可以將數據集恢復到 FLUSHALL 執行之前的狀態。
4. 劣勢
- 對于相同的數據集來說,AOF 文件的體積通常要大于 RDB 文件的體積。
- 根據所使用的 fsync 策略,AOF 的速度可能會慢于 RDB 。 在一般情況下, 每秒 fsync 的性能依然非常高, 而關閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 可以提供更有保證的***延遲時間。
總結
對于我們應該選擇RDB還是AOF,取決于具體的應用場景,官方的建議是兩個同時使用。這樣可以提供更可靠的持久化方案。其實RDB和AOF兩種方式也可以同時使用,在這種情況下,如果redis重啟的話,則會優先采用AOF方式來進行數據恢復,這是因為AOF方式的數據恢復完整度更高。
如果你沒有數據持久化的需求,也完全可以關閉RDB和AOF方式,這樣的話,redis將變成一個純內存數據庫,就像memcache一樣。