Redis 持久化原理分析和使用建議
一、Redis 為什么需要進行持久化
Redis 為了保證性能,會將所有數據存放在內存中,這極大的提高了 Redis 的響應速度,但是這也引入了一個十分嚴重的問題:一旦服務器宕機,內存中的數據將全部丟失。這對于一款數據庫產品來說是不能接受的。要避免數據丟失,最好將內存數據持久化到磁盤等永久存儲介質上。服務重啟時,先加載磁盤文件內的數據到內存,完成數據恢復。目前,Redis 主要提供了三大持久化機制,即 AOF(Append Only File)日志、RDB 快照、AOF 與 RDB 相結合的混合持久化。
二、AOF
2.1 AOF 概述
AOF 全稱為 Append Only File,即實時將每一條寫命令記錄到 AOF 文件中,當 Redis 服務重啟時會順序執(zhí)行一遍 AOF 文件中的所有命令,以達到數據恢復的目的。我們常見的日志記錄方式主要為兩種,一種為寫前日志(Write Ahead Log, WAL),以 Mysql 數據庫為例,Mysql 在將數據寫入磁盤前,會先把修改的數據記到 redo 和 bin log 日志文件中,以便故障時進行恢復。
而另一種則是 Redis AOF 日志所采用的寫后日志,“寫后”的意思是 Redis 會先執(zhí)行所接收到的寫命令,把數據寫入內存,然后才將命令寫入 AOF 日志文件。Redis 在將命令記錄到 AOF 日志中的時候,并不會去檢驗命令語法的正確性。因此先執(zhí)行命令,利用命令執(zhí)行這一環(huán)節(jié)對這些命令進行語法校驗,這樣既避免了寫日志時額外的檢查開銷,也不會將存在語法錯誤的命令記錄到 AOF 日志中,保證了 Redis 在使用 AOF 日志恢復數據時的正確性。
2.2 AOF 開啟方式
由于 AOF 功能開啟后對 Redis 性能會產生一定影響,因此 AOF 功能默認是關閉的。我們可以通過修改 redis.conf 配置文件中的配置參數來開啟 AOF 功能,還可以通過“config set”命令在線動態(tài)的開啟和關閉 AOF 功能。
2.3 AOF 配置項
我們可以通過 redis.conf 配置文件中與 AOF 相關的配置項來自定義相應的 AOF 功能,例如:
- 可以通過appendfilename、appenddirname 配置項來指定 AOF 文件名以及文件存放目錄。
- 可以通過 appendfsync 配置項來指定 AOF 刷盤策略,其中不同的配置項值在數據安全性以及服務性能方面的表現都不相同。
- 配置項 no-appendfsync-on-rewrite 表示是否在 AOF 重寫或寫 RDB 期間,進行 AOF 刷盤。在 AOF 重寫和寫 RDB 文件期間會產生大量的磁盤 IO 讀寫操作,如果配置為 no,則表示在此期間可以進行刷盤操作,此時如果需要同步的數據量非常大可能會阻塞 Redis 主線程,降低 Redis 服務性能。因此在生產環(huán)境中,如果存在大量并發(fā)的寫操作則可將該值設置為 yes;如果為大量并發(fā)的讀操作則可以設置為 no。
- 可以通過 auto-aof-rewrite-percentage、auto-aof-rewrite-min-size 配置項來指定觸發(fā) AOF 重寫的條件,以防止 AOF 文件過度膨脹,占用大量存儲空間。
- 在進行 AOF 持久化期間,可能由于 Redis 服務突然宕機等原因導致寫入 AOF 文件的最后一條命令不完整。在 Redis 服務重啟并加載 AOF 文件時,配置項 aof-load-truncated 的值決定了 Redis 服務能否重啟成功。若值為 yes,則刪除不完整的命令,Redis 服務正常啟動;若值為 no,則 Redis 服務無法啟動。
- 可以通過 aof-use-rdb-preamble 配置項來指定是否開啟混合持久化功能。
2.4 AOF 文件內容
通過上述方式開啟 AOF 功能后,我們以一條 “set testkey testvalue” 命令來看一下命令是以怎樣的格式存儲在 AOF 文件中的。如圖所示其中,“*3” 表示當前命令有三個部分,每部分都是由 “$+數字” 開頭,后面緊跟著具體的命令、鍵或值。這里,“數字”表示這部分中的命令、鍵或值一共有多少字節(jié)。例如,“$3” 表示這部分數據有 3 個字節(jié),也就是 “set” 命令。
2.5 寫回策略
Redis 是先執(zhí)行寫操作命令,然后將該命令記錄到 AOF 緩沖區(qū)中,最后再將緩沖區(qū)中的數據寫入磁盤。這么做避免了額外的檢查開銷,不會阻塞當前寫操作命令的執(zhí)行。AOF 配置項 appendfsync 的參數值,為 AOF 日志寫回磁盤的時機提供了三個選擇。
- Always,同步寫回:每個寫命令執(zhí)行完,立馬將日志寫回磁盤;“同步寫回”可以做到基本不丟數據,但是在每一個寫命令執(zhí)行后都會有一個對磁盤的寫操作,這不可避免地會影響主線程性能。
- Everysec,每秒寫回:每個寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內存緩沖區(qū),每隔一秒把緩沖區(qū)中的內容寫入磁盤;“每秒寫回”采用一秒寫回一次的頻率,避免了“同步寫回”的性能開銷,減少了對 Redis 性能的影響,但是如果發(fā)生宕機,將丟失一秒內的所有寫操作。
- No,操作系統(tǒng)控制的寫回:每個寫命令執(zhí)行完,只是先把日志寫到 AOF 文件的內存緩沖區(qū),由操作系統(tǒng)決定何時將緩沖區(qū)內容寫回磁盤。由于 Redis 已不再掌握落盤的時機,因此一旦宕機沒有寫入 AOF 文件的數據就丟失了。
寫回策略對比:
配置項 | 寫回策略 | 優(yōu)點 | 缺點 |
Always | 同步寫回 | 可靠性高,基本不丟失數據 | 性能影響較大,每一個寫命令后都需要進行落盤操作 |
Everysec | 每秒寫回 | 性能適中 | 可能丟失1秒內的數據 |
No | 操作系統(tǒng)控制寫回 | 性能好 | 可靠性差,可能丟失大量數據 |
2.6 AOF 重寫
在上面我們向 AOF 文件中記錄了一條 set 命令,如果此時我們對該 key 的值進行了更新,那么 AOF 文件中記錄的日志是否也會隨之更新呢?通過 AOF 文件我們可以看到,原先寫入AOF 文件的 set 命令并沒有發(fā)生改變,而是在 AOF 文件末尾追加了一條 set testkey value 命令。
由于 AOF 日志文件中記錄的是自 Redis 啟動后每一條寫操作,因此隨著寫操作的不斷增加,AOF 文件將越來越膨脹,為了防止 AOF 文件不斷地膨脹,Redis 為我們提供了一種 AOF 文件的重寫機制。AOF 文件在重寫時,Redis 會根據數據庫的現狀創(chuàng)建一個新的 AOF 文件,即讀取數據庫中的所有鍵值對,然后用一條命令將每一個鍵值對記錄到新的 AOF 文件中。比如說,當讀取了鍵值對 “testkey”: “value” 之后,重寫機制會記錄 set testkey value 這條命令。
這樣,當需要恢復時,可以重新執(zhí)行該命令,實現 “testkey”: “value” 的寫入,而并不關心中間對這個 key 的其他寫操作。以下圖為例,當我們對一個列表先后做了 6 次修改操作后,列表的最后狀態(tài)是 [“D”, “E”, “F”],此時,只用 LPUSH mylist “D”, “E”, "F" 這一條命令就能實現該數據的恢復,這就節(jié)省了五條命令的空間。這樣對于被頻繁修改過的鍵值對來說,重寫能節(jié)省大量的空間。
注:圖片來源于 https://time.geekbang
雖然 AOF 重寫后,日志文件會縮小,但是,要把整個數據庫最新數據的操作日志都寫回磁盤,仍然是一個非常耗時的過程。為了避免阻塞主線程,導致數據庫性能下降,重寫過程是由 bgrewriteaof 子進程來完成。每次執(zhí)行重寫時,主線程 fork 出 bgrewriteaof 子進程。由子進程逐一把內存中的數據寫成操作,記入重寫日志。由于主線程未阻塞,仍然可以處理新來的操作。
此時,如果有寫操作,Redis 會把這個操作寫到 AOF 日志緩沖區(qū)。這樣一來,即使宕機了,這個 AOF 日志的操作仍然是齊全的,可以用于恢復。并且這個操作也會被寫到重寫日志的緩沖區(qū)。這樣,重寫日志也不會丟失最新的操作。等到內存數據的所有操作記錄重寫完成后,重寫日志記錄的這些最新操作也會寫入新的 AOF 文件,以保證數據庫最新狀態(tài)的記錄。此時,就可以用新的 AOF 文件替代舊文件了。
注:圖片來源于 https://time.geekbang
觸發(fā) AOF 后臺重寫的條件
- AOF 重寫可以由用戶通過調用 `BGREWRITEAOF` 手動觸發(fā)。
- 每次當 `serverCron`(服務器周期性操作函數)函數執(zhí)行時,它會檢查以下條件是否全部滿足,如果全部滿足的話,就觸發(fā)自動的 AOF 重寫操作:
(1)沒有 BGSAVE 命令(RDB 持久化)/ AOF 持久化在執(zhí)行;
(2)沒有 BGREWRITEAOF 在進行;
(3)當前 AOF 文件大小要大于
`server.aof_rewrite_min_size`(默認為1MB),或者在 `redis.conf` 配置了 `auto-aof-rewrite-min-size` 大小;
(4)當前 AOF 文件大小和最后一次重寫后的大小之間的比率大于或者等于指定的增長百分比(在配置文件設置了 `auto-aof-rewrite-percentage` 參數,不設置默認為100%)
2.7 AOF 持久化詳細流程
- Redis 在接收到一條客戶端發(fā)送過來的寫命令后,會先執(zhí)行該寫命令,待命令執(zhí)行成功后將該命令按照 Redis 通信協(xié)議格式寫入 AOF 緩沖區(qū)。
- 根據設置的 AOF 寫回策略,當滿足寫回條件時,將 AOF 緩沖區(qū)中的數據寫入磁盤 AOF 文件中。
- 當 AOF 文件大小達到重寫條件后,主進程 fork 出 bgrewriteaof 子進程,由子進程將內存中的數據重寫為寫命令寫入臨時文件。
- 若重寫期間,主線程執(zhí)行了新的寫操作,主線程在將該寫操作記錄到 AOF 緩沖區(qū)的同時會將該寫操作寫入重寫緩存區(qū)。
- 待子進程完成內存中數據的記錄后,會將重寫緩存區(qū)記錄的數據一并寫入臨時文件。
- 最后利用臨時文件替換原來的 AOF 文件。
2.8 AOF 日志優(yōu)缺點
優(yōu)點:
(1)AOF 可以更好的保護數據不丟失,一般 AOF 會每隔1秒,通過一個后臺線程執(zhí)行 fsync 操作,最多丟失 1 秒鐘的數據。
(2)Redis 提供了 AOF 文件的重寫機制,因此 AOF 日志文件不會膨脹的很大,并且在重寫期間也不會影響客戶端的讀寫。
(3)AOF 文件中保存的是執(zhí)行的指令,所以這個特性非常適合做災難性的誤操作緊急恢復。
缺點:
(1)由于 AOF 文件中記錄的是寫操作的命令,因此對于同一份數據來說,AOF 的日志文件通常要比 RDB 的數據快照文件要大。
(2)AOF 開啟之后,Redis 服務支持的寫 QPS 會比 RDB 支持的寫 QPS 低。
三、RDB
3.1 RDB 概述
RDB 持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。所謂內存快照,就是指內存中的數據在某一個時刻的狀態(tài)記錄。這樣一來,即使宕機,快照文件也不會丟失,數據的可靠性也就得到了保證。這個快照文件就稱為 RDB 文件,其中,RDB 就是 Redis DataBase 的縮寫。因為 RDB 記錄的是某一時刻的數據,并不是操作,所以,在做數據恢復時,我們可以直接把 RDB 文件讀入內存,很快地完成數據恢復。
3.2 RDB 觸發(fā)方式
3.2.1 save 觸發(fā)方式
save 命令執(zhí)行時將阻塞 Redis 服務器,在此期間 Redis 不能執(zhí)行其他客戶端的命令,直到 RDB 過程完成為止。執(zhí)行完成后,用新的 RDB 文件替代掉舊的 RDB 文件。由于 save 命令會阻塞 Redis 服務器,極大的影響了 Redis 的性能,因此不推薦 save 命令觸發(fā)方式。
3.2.2 bgsave 觸發(fā)方式
執(zhí)行該命令時,Redis 會在后臺異步將數據記錄到 RDB 文件中,同時 Redis 還可以響應客戶端請求。bgsave 命令執(zhí)行時 Redis 主進程將執(zhí)行 fork 操作創(chuàng)建子進程,RDB 持久化過程由子進程負責,完成后用新的 RDB 文件替換掉舊的 RDB 文件。阻塞只發(fā)生在 fork 階段,一般時間很短?;旧?Redis 內部所有的 RDB 操作都是采用 bgsave 命令。
3.2.3 自動觸發(fā)
自動觸發(fā)是由我們的配置文件來完成的。在 redis.conf 配置文件中,里面有如下配置:
save:這里是用來配置觸發(fā) Redis 的 RDB 持久化條件,也就是什么時候將內存中的數據保存到硬盤。比如 “save m n”。表示 m 秒內數據集存在 n 次修改時,自動觸發(fā) bgsave。因此自動觸發(fā)的整體流程和 bgsave 命令觸發(fā)是一致的。
RDB 觸發(fā)方式對比
命令 | save | bgsave |
IO類型 | 同步 | 異步 |
阻塞? | 是 | 是(阻塞發(fā)生在fork 階段) |
優(yōu)點 | 不會消耗額外內存 | 不阻塞 Redis 服務器 |
缺點 | 阻塞 Redis 服務器 | 需要 fork,消耗內存 |
3.3 RDB 配置項
在 redis.conf 配置文件中除了用于開啟 RDB 功能的 save 配置項外,還有其他用于自定義 RDB 功能的配置項,例如:
- 開啟 RDB 功能后,可以將配置項 stop-writes-on-bgsave-error 的值設置為 yes,在 bgsave 命令執(zhí)行失敗后阻止后續(xù)的寫命令執(zhí)行。通過這種方式可以讓用戶意識到數據并沒有被成功的持久化到磁盤,從而避免數據丟失問題。
- 可以通過配置項 rdbcompression 來啟用 LZF 對 RDB 文件進行壓縮,雖然壓縮后 RDB 文件占用的存儲空間變小,但是壓縮過程會消耗系統(tǒng)資源,降低 Redis 服務性能。
- 從 RDB 5 開始,RDB 文件將 CRC64 校驗和寫入文件末尾,以保證 RDB 文件的正確性,但這在保存和加載 RDB 文件時,對性能會產生影響。因此可以通過將配置項 rdbchecksum 的值設置為 no,禁用校驗和。
- 可以通過配置項 dbfilename 和 dir 來指定 RDB 文件名和生成目錄。
3.4 寫時復制技術
當由 bgsave 方式觸發(fā) RDB 持久化時,Redis 在寫 RDB 期間是可以正常處理客戶端發(fā)來的寫命令的。因此為了節(jié)省在 RDB 持久化過程中內存的使用,Redis 會借助操作系統(tǒng)提供的寫時復制技術(Copy-On-Write, COW)。簡單來說,bgsave 子進程是由主線程 fork 生成的,可以共享主線程的所有內存數據。bgsave 子進程運行后,開始讀取主線程的內存數據,并把它們寫入 RDB 文件。
此時,如果主線程對這些數據也都是讀操作(例如圖中的鍵值對 A),那么,主線程和 bgsave 子進程相互不影響。但是,如果主線程要修改一塊數據(例如圖中的鍵值對 C),那么,這塊數據就會被復制一份,生成該數據的副本(鍵值對 C’)。然后,主線程在這個數據副本上進行修改。同時,bgsave 子進程可以繼續(xù)把原來的數據(鍵值對 C)寫入 RDB 文件。這既保證了快照的完整性,也允許主線程同時對數據進行修改,避免了對正常業(yè)務的影響。
注:圖片來源于 https://time.geekbang
3.5 RDB 文件格式
RDB 文件由以下五個部分組成;
- SOF 為一個長度為 5 的字符串常量 REDIS;用于標識 RDB 文件的開始,在加載 RDB 文件時可以快速識別該文件是否為 RDB 文件。
- rdb_version 占 4 個字節(jié),表示 RDB 文件的版本號。
- database 是 RDB 文件中的重要部分,包含了多個非空的數據庫。每個 database 又由3個部分組成:SODB 占一個字節(jié)表明數據庫的開始,db_number 表示數據庫的編號,key-value-pairs 表示當前數據庫中的鍵值對。
- EOF 占一個字節(jié),用于標識數據的結束,校驗和的開始。
- check_sum 校驗和,用于校驗 RDB 文件數據是否出現損壞。
3.6 RDB 快照優(yōu)缺點
優(yōu)點:
(1)RDB 會生成多個數據文件,每個數據文件都代表了某一個時刻中 Redis 的數據,這種多個數據文件的方式,非常適合做冷備。
(2)由于 Redis 是通過子進程執(zhí)行磁盤 IO 操作來記錄 RDB 文件的,因此 RDB 機制對 Redis 的性能影響非常小,阻塞只發(fā)生在 fork 階段,可以保持 Redis 的高性能。
(3)相對于 AOF 持久化機制來說,直接基于 RDB 數據文件來重啟和恢復 Redis 進程,更加的快速。
缺點:
(1)由于 RDB 文件記錄的是某個時間段內的數據集,因此當服務器發(fā)生宕機時,該時間段后的數據將丟失。
(2)主進程每次在 fork 子進程來記錄數據時,如果數據文件特別大,可能會導致主進程阻塞數毫秒,或者甚至數秒。
四、混合持久化方式
由于 RDB 文件記錄的是某個時間段內的數據集,因此兩次 RDB 期間的數據依然存在丟失的風險,但是制作 RDB 文件的頻率太高又會對 Redis 性能帶來影響。因此為了避免兩次 RDB 期間數據的丟失,并且降低對性能所帶來的影響, Redis 4.0 提出了一個混合使用 AOF 日志和內存快照的方法。可以通過 redis.conf 配置文件中的配置項 aof-use-rdb-preamble 來開啟混合持久化功能。
利用 AOF 日志記錄兩次快照間的操作,因此, AOF 文件也不會太大,也可以避免重寫開銷。如下圖所示,T1 和 T2 時刻的修改,用 AOF 日志記錄,等到第二次做全量快照時,就可以清空 AOF 日志,因為此時的修改都已經記錄到快照中了,恢復時就不再用日志了。
注:圖片來源于 https://time.geekbang
五、持久化方案選擇
Redis 官方推薦使用混合持久化方案,但我們也需要根據具體的業(yè)務需求和場景并結合三種持久化方案的優(yōu)缺點選擇合適的持久化方案。
- 如果對數據安全性有較高的要求,就可以選擇 AOF 或 混合持久化方案,其中 AOF 持久化方案可通過選擇不同的寫回策略來達到不同程度的數據安全性保證。
- 如果希望數據快速恢復,減少故障恢復時間,可以選擇 RDB 或混合持久化方案。
- 如果對持久化文件大小有要求,可以選擇 RDB 持久化方案,并可以啟用 LZF 對 RDB 文件進行壓縮。
- 如果對 Redis 服務性能有較高的要求,可以選擇 RDB 或混合持久化方案。
- 若 Redis 僅用于緩存,可不開啟持久化功能以追求最大的服務性能。