持久化簡介
什么是持久化?
利用永久性存儲介質將數據進行保存,在特定的時間將保存的數據進行恢復的工作機制稱為持久化。
持久化用於防止數據的意外丟失,確保數據安全性。
我們知道一點,計算機中的數據全部都是二進制,如果現在要保存一組數據的話,有什么樣的方式呢?其實最簡單的就是現在長什么樣,我記下來就行了,那么這種記錄純粹的數據就叫做快照存儲,也就是它保存的是某一時刻的數據狀態。
還有一種形式,它不記錄你的數據,它記錄你所有的操作過程,比如說大家用 idea 的時候,有沒有遇到過寫錯了 ctrl+z 撤銷,然后 ctrl+y 還能恢復,這個地方它也是在記錄,但是記錄的是你所有的操作過程。既然這樣,在操作過程中都給你記錄下來了,你說數據還會丟嗎?肯定不會丟,因為你所有的操作過程我都保存了。這種保存操作過程的存儲,用專業術語來說可以說是日志,以上是兩種不同的保存數據的形式。
Redis 持久化方案
雖說 Redis 是內存數據庫,但是它為數據的持久化提供了兩種技術。分別是「RDB 快照和 AOF 日志 」。
這兩種技術都會用各用一個日志文件來記錄信息,但是記錄的內容是不同的。
- RDB 文件的內容是二進制數據。
- AOF 文件的內容是操作命令。
總結:
- 第一種:將當前數據狀態進行保存,即快照形式,存儲數據結果,存儲格式簡單,關注點在數據。
- 第二種:將數據的操作過程進行保存,即日志形式,存儲操作過程,存儲格式復雜,關注點在數據的操作過程。
RDB
RDB 簡介
RDB 是 Redis 默認開啟的持久化方案,通過快照來將數據持久化到磁盤中。
所謂的快照,就是記錄某一個瞬間東西,比如當我們給風景拍照時,那一個瞬間的畫面和信息就記錄到了一張照片。
所以,RDB 快照就是記錄某一個瞬間的內存數據,記錄的是實際數據;而 AOF 文件記錄的是命令操作的日志,而不是實際的數據。
因此在 Redis 恢復數據時,RDB 恢復數據的效率會比 AOF 高些,因為直接將 RDB 文件讀入內存就可以,不需要像 AOF 那樣還需要額外執行操作命令的步驟才能恢復數據。
Redis 提供了兩個命令來生成 RDB 文件,分別是save
和bgsave
,他們的區別就在於是否在「主線程」里執行:
-
執行了 save 命令,就會在主線程生成 RDB 文件,由於和執行操作命令在同一個線程,所以如果寫入 RDB 文件的時間太長,會阻塞主線程。
-
執行了 bgsave 命令,會創建一個子進程來生成 RDB 文件,這樣可以避免主線程的阻塞。
RDB 文件的加載工作是在服務器啟動時自動執行的,Redis 並沒有提供專門用於加載 RDB 文件的命令。
save 指令
操作與配置
執行命令:
# 手動執行一次保存操作
save
redis.conf 配置文件 save 指令相關:
# 設置持久化文件名,默認值為 dump.rdb,通常設置為 dump-端口號.rdb
dbfilename filename
# 設置存儲.rdb文件的路徑,通常設置成存儲空間較大的目錄中。如目錄名稱為 data
dir path
# 設置存儲至本地數據庫時是否壓縮數據,默認 yes;若設置為 no 則節省 CPU 運行時間,但存儲文件變大
rdbcompression yes|no
# 設置讀寫文件過程是否進行RDB格式校驗,默認 yes;若設置為 no,節約讀寫 10% 時間消耗,單存在數據損壞的風險
rdbchecksum yes|no
工作原理
以上圖為例,現在有四個客戶端各自要執行一個指令,把這些指令發送到 redis 服務器后,他們的執行會有一個先后順序問題,假定就是按照 1234 的順序放過去的話,那會是什么樣的呢?
Redis 是單線程的工作模式,它會創建一個任務隊列,所有的命令都會進到這個隊列里邊,在這兒排隊執行,執行完一個消失一個,當所有的命令都執行完了,OK,結果達到了。
但是如果現在我們執行 save 指令保存大數據量時,會是什么現象呢?
它會非常耗時,以至於它在執行時,后面的指令都要等,所以說這種模式是不友好的,這是 save 指令的一個問題,當 cpu 執行時會阻塞 redis 服務器,直到它執行完畢,所以這里不建議在線上環境用 save 指令。
bgsave 指令
操作與配置
上問講到了當 save 指令的數據量過大時,單線程執行方式造成效率過低,那應該如何處理呢?
此時我們可以使用 bgsave 指令,bg 其實是 background(后台執行)的意思。
執行命令:
# 手動啟動后台保存操作,但不是立即執行
bgsave
redis.conf 配置文件 bgsave 指令相關:
# 后台存儲過程中如果出現錯誤現象,是否停止保存操作,默認 yes
stop-writes-on-bgsave-error yes|no
# 其他
dbfilename filename
dir path
rdbcompression yes|no
rdbchecksum yes|no
工作原理
當執行 bgsave 時,客戶端發出 bgsave 指令給到 redis 服務器。注意,這個時候服務器會馬上回一個結果告訴客戶端后台已經開始了,與此同時它會創建一個子進程,使用 Linux 的 fork 函數創建一個子進程,讓這個子進程去執行 save 相關的操作。此時可以想一下,我們主進程一直在處理指令,而子進程在執行后台的保存,它會不會干擾到主進程的執行呢?
答案是不會,所以說它才是主流方案。子進程開始執行之后,它就會創建 RDB 文件把它存起來,操作完以后他會把這個結果返回,也就是說 bgsave 的過程分成兩個過程:第一個是服務端拿到指令直接告訴客戶端開始執行了;另一個過程是一個子進程在完成后台的保存操作,操作完以后回一個消息。
bgsave 配置執行
相關配置
Redis 還可以通過配置文件的選項來實現每隔一段時間自動執行一次 bgsave 命令,(redis.conf)默認會提供以下配置:
# 設置自動持久化的條件,滿足限定時間范圍內 key 的變化數量達到指定數量即進行持久化
save second changes
別看選項名叫 save,實際上執行的是 bgsave 命令,也就是會創建子進程來生成 RDB 快照文件。其參數含義為:
- second:監控時間范圍
- changes:監控 key 的變化量
示例:
只要滿足上面條件的任意一個,就會執行 bgsave,它們的意思分別是:
900 秒發生 1 次操作,就會持久化存儲;
300 秒發生 10 次操作,就會持久化存儲;
60 秒發生 10000 次操作,就會持久化存儲。
執行 bgsave 過程中,Redis 依然可以繼續處理操作命令的,也就是數據是能被修改的。
工作原理
RDB 啟動方式對比
方式 | save 指令 | bgsave 指令 |
---|---|---|
讀寫 | 同步 | 異步 |
阻塞客戶端指令 | 是 | 否 |
額外內存消耗 | 否 | 是 |
啟動新進程 | 否 | 是 |
RDB特殊啟動形式:
# 服務器運行過程中重啟
debug reload
# 關閉服務器並保存數據
shutdown save
RDB 優缺點
RDB 優點:
- RDB 是一個緊湊壓縮的二進制文件,存儲效率較高。
- RDB 內部存儲的是 redis 在某個時間點的數據快照,非常適合用於數據備份,全量復制等場景。
- RDB 恢復數據的速度要比 AOF 快很多。
- 應用:服務器中每 X 小時執行 bgsave 備份,並將 RDB 文件拷貝到遠程機器中,用於災難恢復。
RDB 缺點:
-
注意,Redis 的快照是全量快照,也就是說每次執行快照,都是把內存中的「所有數據」都記錄到磁盤中。
所以可以認為,執行快照是一個比較重的操作,如果頻率太頻繁,可能會對 Redis 性能產生影響。如果頻率太低,服務器故障時,丟失的數據會更多。
通常可能設置至少 5 分鍾才保存一次快照,這時如果 Redis 出現宕機等情況,則意味着最多可能丟失 5 分鍾數據。 -
這就是 RDB 快照最明顯的缺點,在服務器發生故障時,丟失的數據會比 AOF 持久化的方式更多,因為 RDB 快照是全量快照的方式,因此執行的頻率不能太頻繁,否則會影響 Redis 性能;而 AOF 日志可以以秒級的方式記錄操作命令,所以丟失的數據就相對更少。
-
此外,Redis 的眾多版本中未進行 RDB 文件格式的版本統一,有可能出現各版本服務之間數據格式無法兼容現象。
AOF
AOF 簡介
AOF 方式的持久化,是每操作一次 Redis 數據庫,就將操作的記錄存儲到 AOF 持久化日志文件中。
AOF 的主要作用是解決了數據持久化的實時性,目前已經是 Redis 持久化的主流方式。
這種保存寫操作命令到日志的持久化方式,就是 Redis 里的 AOF(Append Only File) 持久化功能,注意只會記錄寫操作命令,讀操作命令是不會被記錄的,因為沒意義。
AOF 日志文件其實就是普通的文本,我們可以通過 cat 命令查看里面的內容,不過里面的內容如果不知道一定的規則的話,可能會看不懂。
這里以「set name xiaolin」命令作為例子,Redis 執行了這條命令后,記錄在 AOF 日志里的內容如下圖:
解釋:
- 「*3」表示當前命令有三個部分,每部分都是以「$+數字」開頭,后面緊跟着具體的命令、鍵或值。
- 然后,這里的「數字」表示這部分中的命令、鍵或值一共有多少字節。例如「$3 set」表示這部分有 3 個字節,也就是「set」命令這個字符串的長度。
Redis 是先執行寫操作命令后,才將該命令記錄到 AOF 日志里的,這么做其實有兩個好處:
-
第一個好處:避免額外的檢查開銷。
因為如果先將寫操作命令記錄到 AOF 日志里,再執行該命令的話,如果當前的命令語法有問題,那么如果不進行命令語法檢查,該錯誤的命令記錄到 AOF 日志里后,Redis 在使用日志恢復數據時,就可能會出錯。
而如果先執行寫操作命令再記錄日志的話,只有在該命令執行成功后,才將命令記錄到 AOF 日志里,這樣就不用額外的檢查開銷,保證記錄在 AOF 日志里的命令都是可執行並且正確的。 -
第二個好處:不會阻塞當前寫操作命令的執行。因為當寫操作命令執行成功后,才會將命令記錄到 AOF 日志。
AOF 配置
在 Redis 中 AOF 持久化功能默認是不開啟的,需要我們修改 redis.conf 配置文件中的以下參數:
# 開啟AOF持久化功能,默認no,即不開啟狀態
appendonly yes|no
# AOF持久化文件名,默認文件名為appendonly.aof,建議配置為appendonly-端口號.aof
appendfilename filename
# AOF持久化文件保存路徑,與RDB持久化文件保持一致即可
dir
# AOF寫數據策略,默認為everysec
appendfsync always|everysec|no
AOF 風險
當然,AOF 持久化功能也不是沒有潛在風險。
-
第一個風險,執行寫操作命令和記錄日志是兩個過程,那當 Redis 在還沒來得及將命令寫入到硬盤時,服務器發生宕機了,這個數據就會有丟失的風險。
-
第二個風險,前面說道,由於寫操作命令執行成功后才記錄到 AOF 日志,所以不會阻塞當前寫操作命令的執行,但是可能會給「下一個」命令帶來阻塞風險。
因為將命令寫入到日志的這個操作也是在主進程完成的(執行命令也是在主進程),也就是說這兩個操作是同步的。
如果在將日志內容寫入到硬盤時,服務器的硬盤的 I/O 壓力太大,就會導致寫硬盤的速度很慢,進而阻塞住了,也就會導致后續的命令無法執行。
認真分析一下,其實這兩個風險都有一個共性,都跟「 AOF 日志寫回硬盤的時機」有關。
AOF 三種回寫策略
Redis 寫入 AOF 日志的過程,如下圖:
-
Redis 執行完寫操作命令后,會將命令追加到 server.aof_buf 緩沖區;
-
然后通過 write() 系統調用,將 aof_buf 緩沖區的數據寫入到 AOF 文件,此時數據並沒有寫入到硬盤,而是拷貝到了內核緩沖區 page cache,等待內核將數據寫入硬盤;
-
具體內核緩沖區的數據什么時候寫入到硬盤,由內核決定。
Redis 提供了 3 種寫回硬盤的策略,控制的就是上面說的第三步的過程。
在 redis.conf 配置文件中的appendfsync
配置項可以有以下 3 種參數可填:
-
Always:這個單詞的意思是「總是」,所以它的意思是每次寫操作命令執行完后,同步將 AOF 日志數據寫回硬盤。
-
Everysec:這個單詞的意思是「每秒」,所以它的意思是每次寫操作命令執行完后,先將命令寫入到 AOF 文件的內核緩沖區,然后每隔一秒將緩沖區里的內容寫回到硬盤;也是默認策略。
-
No:意味着不由 Redis 控制寫回硬盤的時機,轉交給操作系統控制寫回的時機,也就是每次寫操作命令執行完后,先將命令寫入到 AOF 文件的內核緩沖區,再由操作系統決定何時將緩沖區內容寫回硬盤。
這 3 種寫回策略都無法能完美解決「主進程阻塞」和「減少數據丟失」的問題,因為兩個問題是對立的,偏向於一邊的話,就會要犧牲另外一邊,原因如下:
-
Always 策略的話,可以最大程度保證數據不丟失,但是由於它每執行一條寫操作命令就同步將 AOF 內容寫回硬盤,所以是不可避免會影響主進程的性能。
-
No 策略的話,是交由操作系統來決定何時將 AOF 日志內容寫回硬盤,相比於 Always 策略性能較好,但是操作系統寫回硬盤的時機是不可預知的,如果 AOF 日志內容沒有寫回硬盤,一旦服務器宕機,就會丟失不定數量的數據。
-
Everysec 策略的話,是折中的一種方式,避免了 Always 策略的性能開銷,也比 No 策略更能避免數據丟失,當然如果上一秒的寫操作命令日志沒有寫回到硬盤,發生了宕機,這一秒內的數據自然也會丟失。
大家可以根據自己的業務場景進行選擇:
-
如果要高性能,就選擇 No 策略。
-
如果要高可靠,就選擇 Always 策略。
-
如果允許數據丟失一點,但又想性能高,就選擇 Everysec 策略。
AOF 重寫
AOF 重寫介紹
場景:如果連續執行如下寫數據的指令時,該如何處理?
隨着命令不斷寫入 AOF,文件會越來越大,為了解決這個問題,Redis 引入了 AOF 重寫機制壓縮文件體積。AOF 文件重寫是將 Redis 進程內的數據轉化為寫命令同步到新 AOF 文件的過程。簡單說就是將對同一個數據的若干個條命令執行結果轉化成最終結果數據對應的指令進行記錄。
AOF重寫作用:
- 降低磁盤占用量,提高磁盤利用率。
- 提高持久化效率,降低持久化寫時間,提高 I/O 性能。
- 降低數據恢復用時,提高數據恢復效率。
AOF重寫規則:
-
進程內具有時效性的數據,並且數據已超時將不再寫入文件。
-
非寫入類的無效指令將被忽略,只保留最終數據的寫入命令。
-
如 del key1、 hdel key2、srem key3、set key4 111、set key4 222 等。
-
如 select 指令雖然不更改數據,但是更改了數據的存儲位置,此類命令同樣需要記錄。
-
-
對同一數據的多條寫命令合並為一條命令。
- 如 lpush list1 a、lpush list1 b、lpush list1 c 可以轉化為 lpush list1 a b c。
-
為防止數據量過大造成客戶端緩沖區溢出,對 list、set、hash、zset 等類型,每條指令最多寫入 64 個元素。
AOF 重寫配置
手動重寫:
bgrewriteaof
手動重寫原理分析:
自動重寫:
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
自動重寫觸發比對參數(運行指令 info Persistence 獲取具體信息):
aof_current_size
aof_base_size
自動重寫觸發條件公式:
混合持久化
RDB 和 AOF 對比
持久化方式 | RDB | AOF |
---|---|---|
占用存儲空間 | 小(數據級:壓縮) | 大(指令級:重寫) |
存儲速度 | 慢 | 快 |
恢復速度 | 快 | 慢 |
數據安全性 | 會丟失數據 | 依據策略決定 |
資源消耗 | 高/重量級 | 低/輕量級 |
啟動優先級 | 低 | 高 |
RDB 和 AOF 合體
盡管 RDB 比 AOF 的數據恢復速度快,但是快照的頻率不好把握:
-
如果頻率太低,兩次快照間一旦服務器發生宕機,就可能會比較多的數據丟失;
-
如果頻率太高,頻繁寫入磁盤和創建子進程會帶來額外的性能開銷。
那有沒有什么方法不僅有 RDB 恢復速度快的優點,又有 AOF 丟失數據少的優點呢?
當然有,那就是將 RDB 和 AOF 合體使用,這個方法是在 Redis 4.0 提出的,該方法叫混合使用 AOF 日志和內存快照
,也叫混合持久化。
如果想要開啟混合持久化功能,可以在 Redis 配置文件將下面這個配置項設置成 yes:
aof-use-rdb-preamble yes
混合持久化工作在 AOF 日志重寫過程
。當開啟了混合持久化時,在 AOF 重寫日志時,fork 出來的重寫子進程會先將與主線程共享的內存數據以 RDB 方式寫入到 AOF 文件,然后主線程處理的操作命令會被記錄在重寫緩沖區里,重寫緩沖區里的增量命令會以 AOF 方式寫入到 AOF 文件,寫入完成后通知主進程將新的含有 RDB 格式和 AOF 格式的 AOF 文件替換舊的的 AOF 文件。
也就是說,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量數據,后半部分是 AOF 格式的增量數據
。
這樣的好處在於:
-
重啟 Redis 加載數據的時候,由於前半部分是 RDB 內容,這樣
加載的時候速度會很快
。 -
加載完 RDB 的內容后,才會加載后半部分的 AOF 內容,這里的內容是 Redis 后台子進程重寫 AOF 期間,主線程處理的操作命令,可以
使得數據更少地丟失
。