Redis持久化深度解析
在現今的數據驅動世界中,數據持久化成為了一項至關重要的任務。它不僅需要保證數據的安全,還要提供快速讀寫的功能。
對于許多現代化應用程序和服務來說,Redis被廣泛使用作為一個高性能的鍵值存儲系統。Redis以其卓越的性能和靈活性贏得了開發者們的青睞。然而,這些優點都離不開它強大的持久化機制。
通過本文,我們將深入探討Redis的持久化策略,包括RDB(Redis DataBase)快照和AOF(Append Only File)日志,并解析如何根據自己的業務需求選擇合適的持久化方案。
一、Redis持久化介紹
你也許會問,為什么需要持久化呢?因為Redis作為一款內存數據庫,在進程異常退出或服務器斷電之后,所有的數據都將消失。如果沒有持久化功能,無法保證數據的持久性,那么這樣的數據庫還有什么用呢?
Redis持久化分為兩種:「RDB(Redis DataBase)」和「AOF(Append Only File)」。
RDB是指將Redis內存中的數據定期寫入磁盤上的一個快照文件中,而AOF則是以追加的方式記錄Redis執行的每一條寫命令。
你也可以同時開啟兩種持久化方式,在這種情況下,當Redis重啟的時候會優先載入AOF文件來恢復原始的數據。
接下來,我們將分別介紹RDB和AOF的實現原理。
二、RDB原理
RDB是Redis默認的持久化方式,它將Redis在內存中的數據定期寫入到硬盤中,生成一個快照文件。快照文件是一個二進制文件,包含了Redis在某個時間點的所有數據。
RDB的優點是快速、簡單,適用于大規模數據備份和恢復。但是,RDB也有缺點,例如數據可能會丟失,因為Redis只會在指定的時間點生成快照文件。如果在快照文件生成之后,但在下一次快照文件生成之前服務器宕機,那么這期間的數據就會丟失。
由于RDB文件是以二進制格式保存的,因此它非常緊湊,并且在Redis重啟時可以迅速地加載數據。相比于AOF,RDB文件一般會更小。
RDB持久化有兩種方式:手動和自動。
手動方式通過SAVE命令或BGSAVE命令進行:
- SAVE命令會阻塞Redis服務器,直到快照文件生成完成。
- BGSAVE命令會Fork一個子進程(注意是子進程,不是子線程)在后臺生成快照文件,不會阻塞Redis服務器。
自動方式則是在配置文件中設置, 讓它在“ N 秒內數據集至少有 M 個改動”這一條件被滿足時, 自動保存一次數據集。
比如說,以下設置會讓 Redis 在滿足 “10秒內有至少100 個鍵被改動” 這一條件時, 自動保存一次數據集。
save 10 100
1.Fork函數與寫時復制
在 Redis 中,Fork 函數被用于創建子進程。Redis 的使用場景中通常有大量的讀操作和較少的寫操作,而 Fork 函數可以利用 Linux 操作系統的寫時復制(Copy On Write,即 COW)機制,讓父子進程共享內存,從而減少內存占用,并且避免了沒有必要的數據復制。
我們可以使用 Linux下的 man fork 命令來查看下Fork函數的說明文檔。
翻譯如下:
在Linux下,fork()是使用寫時復制的頁實現的,所以它唯一的代價是復制父進程的頁表以及為子進程創建獨特的任務結構所需的時間和內存。
簡單來說就是 fork()函數會復制父進程的地址空間到子進程中,復制的是指針,而不是數據,所以速度很快。
在 Redis 中,當執行 RDB 持久化操作時,Redis 會調用 fork 函數創建子進程,然后由子進程負責將數據寫入到磁盤中。為了避免父子進程同時對內存中的數據進行修改導致數據不一致。Redis 會啟用寫時復制機制。
這樣,當父進程修改內存中的數據時, Linux 內核會將該部分內存復制一份給子進程使用,從而保證父子進程間的數據互相獨立。
示意圖如下:
當沒有發生寫的時候,子進程和父進程指向地址是一樣的,發生寫的時候,就會拷貝出一塊新的內存區域,實現父子進程隔離。
通過使用 fork 函數和寫時復制機制,Redis 可以高效地執行 RDB 持久化操作,并且不會對 Redis 運行過程中的性能造成太大的影響。同時,這種方式也提供了一種簡單有效的機制來保護 Redis 數據的一致性和可靠性。
不過,需要注意的是:
fork的這個過程主進程是阻塞的,fork完之后不阻塞。RDB 需要經常fork子進程來保存數據集到硬盤上,當數據集比較大的時候,fork的過程是非常耗時的,可能會導致Redis在一些毫秒級內不能響應客戶端的請求,數據集很大的時候,fork過程可能會持續數秒。
可能會因為數據量大而導致主進程長時間被掛起,造成Redis服務不可用。因此,在設計時應盡可能減少數據量或者優化fork的調用頻率。
2.關于寫時復制的思考
上述寫時復制流程貌似有個問題:
比如,有個鍵值對 k1 a 。此時Redis正在bgsave。這時客戶端發來一個請求,主進程發生寫操作set k1 b,由于寫時復制,此時子進程里k1的值還是a。最終持久化的也是a。
為什么不直接持久化新值而持久化舊值?寫時復制的意義是什么?
基于上面的問題,可以給出的解釋主要有兩點:
- 其實Redis為了性能考慮,內存的持久化是一個順序寫的操作。子進程備份RDB是一個順序寫的過程,如果主進程的所有寫入請求都隨時記錄到RDB文件中,那么理論更新的key可能在任何位置出現,就會變為隨機寫,性能低。
- 其次如果主進程一直在寫入更新key的話,那么這次RDB備份一直都在寫主進程寫入的新值,永遠不會停止。
3.RDB相關配置
以下是一些RDB的相關參數配置:
- save:指定 RDB 持久化操作的條件。當 Redis 的數據發生變化,并且經過指定的時間(seconds)和變化次數(changes)后,Redis 會自動執行一次 RDB 操作。例如,save 3600 10000 表示如果 Redis 的數據在一個小時內發生了至少 10000 次修改,那么 Redis 將執行一次 RDB 操作。
- stop-writes-on-bgsave-error:指定在 RDB 持久化過程中如果出現錯誤是否停止寫入操作。如果設置為 yes,當 Redis 在執行 RDB 操作時遇到錯誤時,Redis 將停止接受寫入操作;如果設置為 no,Redis 將繼續接受寫入操作。
- rdbcompression:指定是否對 RDB 文件進行壓縮。如果設置為 yes,Redis 會在生成 RDB 文件時對其進行壓縮,從而減少磁盤占用空間;如果設置為 no,Redis 不會對生成的 RDB 文件進行壓縮。
- rdbchecksum:指定是否對 RDB 文件進行校驗和計算。如果設置為 yes,在保存 RDB 文件時,Redis 會計算一個 CRC64 校驗和并將其追加到 RDB 文件的末尾;在加載 RDB 文件時,Redis 會對文件進行校驗和驗證,以確保文件沒有受到損壞或篡改。
- replica-serve-stale-data:這是 Redis 4.0 中新增的一個配置項,用于指定復制節點在與主節點斷開連接后是否繼續向客戶端提舊數據。當設置為 yes 時,在復制節點與主節點斷開連接后,該節點將繼續向客戶端提供舊數據,直到重新連接上主節點并且同步完全新的數據為止;當設置為 no 時,復制節點會立即停止向客戶端提供數據,并且等待重新連接上主節點并同步數據。需要注意的是,當 replica-serve-stale-data 設置為 yes 時,可能會存在一定的數據不一致性問題,因此建議僅在特定場景下使用。
- repl-diskless-sync:這是 Redis 2.8 中引入的一個配置項,用于指定復制節點在進行初次全量同步(即從主節點獲取全部數據)時是否采用無盤同步方式。當設置為 yes 時,復制節點將通過網絡直接獲取主節點的數據,并且不會將數據存儲到本地磁盤中;當設置為 no 時,復制節點將先將主節點的數據保存到本地磁盤中,然后再進行同步操作。采用無盤同步方式可以避免磁盤 IO 操作對系統性能的影響,但同時也會增加網絡負載和內存占用。因此,應該根據具體的場景和需求選擇合適的同步方式。
三、AOF原理
AOF持久化是按照Redis的寫命令順序將寫命令追加到磁盤文件的末尾。AOF是一種基于日志的持久化方式,它保存了Redis服務器所有寫入操作的日志記錄,以保證數據的持久性、可靠性和完整性。
AOF持久化技術的核心思想是將Redis服務器執行的所有寫命令追加到一個文件中。當Redis服務器重新啟動時,可以通過重新執行AOF文件來恢復服務器的狀態。
AOF有個比較好的優勢是可以恢復誤操作。
舉個例子,如果你不小心執行了 FLUSHALL 命令,導致數據被誤刪了 ,但只要 AOF 文件未被重寫,那么只要停止服務器,移除 AOF 文件末尾的 FLUSHALL 命令,并重啟 Redis ,就可以將數據集恢復到 FLUSHALL 執行之前的狀態。
1.AOF持久化配置
Redis的AOF持久化配置頻率可通過appendfsync 參數進行控制。該參數有以下三個選項:
- always:每次有數據修改都立即寫入磁盤,是最安全的選項。
- everysec:每秒鐘寫入一次,性能和安全之間做了一個平衡。
- no:從不主動寫入,完全依靠操作系統自身的緩存機制來決定何時將數據寫入磁盤。
默認情況下,Redis的appendfsync參數設置為everysec。如果需要提高持久化安全性,可以將其改為always,如果更關注性能,則可以將其改為no。但是需要注意的是,使用no可能會導致數據丟失的風險,建議在應用場景允許的情況下謹慎使用。
2.AOF文件解讀
一個簡單的AOF文件示例如下:
其中:
- *號:表示參數個數,后面緊跟著參數的長度和值。
- $號:表示參數長度,后面緊跟著參數的值。
實際上AOF文件中保存的所有命令都遵循相同的格式,即以*開頭表示參數個數,$開頭表示參數長度,其后緊跟著參數的值。
3.AOF文件修復
服務器可能在程序正在對 AOF 文件進行寫入時停機,造成AOF 文件損壞。
發生這種情況時,可以使用 Redis 自帶的 redis-check-aof 程序,對 AOF 文件進行修復,命令如下:
$ redis-check-aof –fix
4.AOF重寫
Redis的AOF重寫機制指的是將AOF文件中的冗余命令刪除,以減小AOF文件的大小并提高讀寫性能的過程。
Redis的AOF重寫機制采用了類似于復制的方式,首先將內存中的數據快照保存到一個臨時文件中,然后遍歷這個臨時文件,只保留最終狀態的命令,生成新的AOF文件。
具體來說,Redis執行AOF重寫可以分為以下幾個步驟:
- 開始AOF重寫過程,向客戶端返回一個提示信息。
- 創建一個臨時文件,并將當前數據庫中的鍵值對寫入到臨時文件中。
- 在創建的臨時文件中將所有的寫命令都轉換成Redis內部的表示格式,即使用一系列的Redis命令來表示一個操作,例如使用SET命令來表示對某個鍵進行賦值操作。
- 對臨時文件進行壓縮,去掉多余的空格和換行符等,減小文件體積。
- 將壓縮后的內容寫入到新的AOF文件中。
- 停止寫入命令到舊的AOF文件,并將新的AOF文件的文件名替換為舊的AOF文件的文件名。
- 結束AOF重寫過程,并向客戶端發送完成提示信息。
通過AOF重寫機制,Redis可以在不停止服務的情況下減小AOF文件的大小,提高讀寫性能,同時也可以保證數據的一致性。
Redis提供了手動觸發AOF重寫的命令 BGREWRITEAOF 。可以在Redis的客戶端中執行該命令來啟動AOF重寫過程。Redis 2.2 需要自己手動執行 BGREWRITEAOF 命令,到了 Redis 2.4 則可以自動觸發 AOF 重寫。
具體操作步驟如下:
(1)打開redis-cli命令行工具,連接到Redis服務。
(2)執行BGREWRITEAOF命令,啟動AOF重寫過程。
$ redis-cli
127.0.0.1:6379> BGREWRITEAOF
(3) Redis會返回一個后臺任務的ID,表示AOF重寫任務已經開始。
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started by pid 1234
(4)可以使用INFO PERSISTENCE命令查看當前AOF文件的大小和重寫過程的狀態,等待重寫完成即可。
127.0.0.1:6379> INFO PERSISTENCE
# Persistence
aof_enabled:1
aof_rewrite_in_progress:1
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:0
aof_current_rewrite_time_sec:14
aof_last_bgrewrite_status:ok
aof_last_write_status:ok
需要注意的是,執行BGREWRITEAOF命令可能會占用較多的CPU和內存資源,因此在生產環境中需要謹慎使用,并確保有足夠的系統資源支持。
同時,即使手動觸發AOF重寫,Redis也會在滿足一定條件時自動觸發AOF重寫,以保證AOF文件的大小和性能。
需要注意的是:
在版本號大于等于 2.4 的 Redis 中,BGSAVE 執行的過程中,不可以執行 BGREWRITEAOF 。反過來說,在 BGREWRITEAOF 執行的過程中,也不可以執行 BGSAVE。目的是防止兩個 Redis 后臺進程同時對磁盤進行大量的 I/O 操作。
5.AOF緩沖區與AOF重寫緩存區
在Redis中,盡管「AOF緩沖區」和「AOF重寫緩沖區」的名稱相似,但它們實際上是兩個不同的概念。
AOF緩沖區是一個用于暫存需要寫入AOF文件的命令的緩沖區。在Redis處理客戶端發來的寫命令時,如果開啟了AOF持久化功能,則該命令將被先寫入到AOF緩沖區。AOF緩沖區中的內容通過配置的規則持久化到磁盤上。持久化規則可以通過配置項appendfsync來調整。
AOF重寫緩沖區是一個用于執行AOF文件的重寫操作的緩沖區。AOF重寫操作是一種將現有AOF文件重寫成最小化的新AOF文件的操作。AOF重寫操作的目的是減少AOF文件的大小,同時加快恢復速度。AOF重寫緩存區在AOF重寫時開始啟用,Redis服務器主進程在執行完寫命令之后,會同時將這個寫命令追加到AOF緩沖區和AOF重寫緩沖區。
示意圖如下:
6.AOF緩沖區可以替代AOF重寫緩沖區嗎
AOF緩沖區不可以替代AOF重寫緩沖區。
原因是AOF重寫緩沖區記錄的是從重寫開始后的所有需要重寫的命令,而AOF緩沖區可能只記錄了部分的命令(如果寫回的話,AOF緩存區的數據就會失效被丟失,因而只會保存一部分的命令,而AOF重寫緩存區不會)。
AOF緩沖區主要是Redis用來解決主進程執行命令速度與磁盤寫入速度不同步所設置的,通過AOF緩沖區可以有效地避免頻繁對硬盤進行讀寫,進而提升性能。Redis在AOF持久化的時候,會先把命令寫入到AOF緩沖區,然后通過寫回策略來寫入硬盤AOF文件。
7.AOF相關配置
在 Redis 的配置文件 redis.conf 中,可以通過以下配置項來設置 AOF 相關參數:
(1)appendonly:該配置項用于開啟或關閉 AOF,默認為關閉。若開啟了 AOF,Redis 會在每次執行寫命令時,將命令追加到 AOF 文件末尾。
(2)appendfilename:用于設置 AOF 文件名,默認為 appendonly.aof。
(3)appendfsync:該配置項用于設置 AOF 的同步機制。有三種可選值:
- always:表示每個寫命令都要同步到磁盤,安全性最高,但是性能較差。
- everysec:表示每秒同步一次,是默認選項,既能保證數據安全,又具有較好的性能。
- no:表示不進行同步,而是由操作系統決定何時將緩沖區中的數據同步到磁盤上,性能最好,但是安全性較低。
(4)auto-aof-rewrite-percentage和auto-aof-rewrite-min-size:這兩個配置項用于設置 AOF 重寫規則。當 AOF 文件大小超過 auto-aof-rewrite-min-size 設置的值,并且 AOF 文件增長率達到 auto-aof-rewrite-percentage 所定義的百分比時,Redis 會啟動 AOF 重寫操作。
auto-aof-rewrite-percentage默認值為100,以及`auto-aof-rewrite-min-size默認值為64mb,也就是說默認Redis會記錄上次重寫時的AOF大小,默認配置是當AOF文件大小是上次rewrite后大小的一倍且文件大于64M時觸發。
(5)aof-use-rdb-preamble:Redis 4版本新特性,混合持久化。AOF重寫期間是否開啟增量式同步,該配置項在AOF重寫期間是否使用RDB文件內容。默認是no,如果設置為yes,在AOF文件頭加入一個RDB文件的內容,可以盡可能的減小AOF文件大小,同時也方便恢復數據。
8.寫后日志
我們比較熟悉的是數據庫的寫前日志(Write Ahead Log,WAL),也就是說,在實際寫數據前,先把修改的數據記到日志文件中,以便故障時進行恢復。
不過,AOF 日志卻正好相反,它是寫后日志,“寫后”的意思是 Redis 是先執行命令,把數據寫入內存,然后才記錄日志。
為什么要這樣設計?
其實為了避免額外的檢查開銷,Redis 在向 AOF 里面記錄日志的時候,并不會先去對這些命令進行語法檢查。所以,如果先記日志再執行命令的話,日志中就有可能記錄了錯誤的命令,Redis 在使用日志恢復數據時,就可能會出錯。
而寫后日志這種方式,就是先讓系統執行命令,只有命令能執行成功,才會被記錄到日志中,否則,系統就會直接向客戶端報錯。所以,Redis 使用寫后日志這一方式的一大好處是,可以避免出現記錄錯誤命令的情況。
除此之外,AOF 寫后日志還有一個好處:它是在命令執行后才記錄日志,所以并不會阻塞當前的寫操作。
不過,寫后日志也有兩個潛在的風險:
- 首先,如果剛執行完一個命令,還沒有來得及記日志就宕機了,那么這個命令和相應的數據就有丟失的風險。如果此時 Redis 是用作緩存,還可以從后端數據庫重新讀入數據進行恢復,但是,如果 Redis 是直接用作數據庫的話,此時,因為命令沒有記入日志,所以就無法用日志進行恢復了。
- 其次,AOF 雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。這是因為,AOF 日志也是在主線程中執行的,如果在把日志文件寫入磁盤時,磁盤寫壓力大,就會導致寫盤很慢,進而導致后續的操作也無法執行了。
四、混合持久化
在過去, Redis 用戶通常會因為 RDB 持久化和 AOF 持久化之間不同的優缺點而陷入兩難的選擇當中:
- RDB 持久化能夠快速地儲存和恢復數據,但是在服務器停機時可能會丟失大量數據。
- AOF 持久化能夠有效地提高數據的安全性,但是在儲存和恢復數據方面卻要耗費大量的時間。
為了讓用戶能夠同時擁有上述兩種持久化的優點, Redis 4.0 推出了一個“魚和熊掌兼得”的持久化方案 —— RDB-AOF 混合持久化。
這種持久化能夠通過 AOF 重寫操作創建出一個同時包含 RDB 數據和 AOF 數據的 AOF 文件, 其中 RDB 數據位于 AOF 文件的開頭, 它們儲存了服務器開始執行重寫操作時的數據庫狀態。至于那些在重寫操作執行之后執行的 Redis 命令, 則會繼續以 AOF 格式追加到 AOF 文件的末尾, 也即是 RDB 數據之后。
也就是說當開啟混合持久化之后,AOF文件中的內容:前半部分是二進制的RDB內容,后面跟著AOF增加的數據,AOF位于兩次RDB之間。
格式會類似下面這樣:
(二進制)RDB
AOF
(二進制)RDB
在目前版本中, RDB-AOF 混合持久化功能默認是處于關閉狀態的, 為了啟用該功能, 用戶不僅需要開啟 AOF 持久化功能, 還需要將 aof-use-rdb-preamble 選項的值設置為 true。
appendonly yes
aof-use-rdb-preamble yes
五、如何選擇合適的持久化方式
當你想選擇適合你的應用程序的持久化方式時,你需要考慮以下兩個因素:
(1)數據的實時性和一致性:如果對數據的實時性和一致性有很高的要求,則AOF可能是更好的選擇。
如果對數據的實時性和一致性要求不太高,并且希望能快速地加載數據并減少磁盤空間的使用,那么RDB可能更適合你的應用程序。因為RDB文件是二進制格式的,結構非常緊湊,所以在Redis重啟時可以迅速地加載數據。
(2)Redis的性能需求:如果對Redis的性能有很高的要求,那么關閉持久化功能也是一個選擇。因為持久化功能可能會影響Redis的性能,但是一般不建議這么做。
本篇文章到這就結束了,最后我們來做個小總結:
我們要意識到Redis的持久化機制扮演著至關重要的角色。RDB和AOF兩種主要的持久化方式各有其優勢和使用場景。
RDB通過提供特定時間點的數據快照,對于災難恢復是非常有效的;而AOF則通過記錄每個寫入操作,提供了更好的數據持久性保證。然而,它們也有各自的局限性,這就需要根據實際需求來權衡選用哪種持久化方式。
最后,不可忽視的是,在選擇合適的持久化策略時,我們還應考慮如何平衡內存使用、磁盤使用、性能與持久性等多個因素。只有對Redis持久化的深入理解,我們才能充分利用其強大的功能,以滿足各種業務需求。
希望這篇文章能夠給你帶來收獲和思考,謝謝。