一文帶你深入瞭解 Redis 的持久化方式及其原理

平頭哥的技術博文發表於2019-11-20

Redis 提供了兩種持久化方式,一種是基於快照形式的 RDB,另一種是基於日誌形式的 AOF,每種方式都有自己的優缺點,本文將介紹 Redis 這兩種持久化方式,希望閱讀本文後你對 Redis 的這兩種方式有更加全面、清晰的認識。

RDB 快照方式持久化

先從 RDB 快照方式聊起,RDB 是 Redis 預設開啟的持久化方式,並不需要我們單獨開啟,先來看看跟 RDB 相關的配置資訊:

################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behaviour will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#   save ""
# 自動生成快照的觸發機制 中間的是時間,單位秒,後面的是變更資料 60 秒變更 10000 條資料則自動生成快照
save 900 1
save 300 10
save 60 10000

# 生成快照失敗時,主執行緒是否停止寫入
stop-writes-on-bgsave-error yes

# 是否採用壓縮演算法儲存
rdbcompression yes

# 資料恢復時是否檢測 RDB檔案有效性
rdbchecksum yes

# The filename where to dump the DB
# RDB 快照生成的檔名稱
dbfilename dump.rdb

# 快照生成的路徑 AOF 也是存放在這個路徑下面
dir .
複製程式碼

關於 RDB 相關配置資訊不多,需要我們調整的就更少了,我們只需要根據自己的業務量修改生成快照的機制和檔案存放路徑即可。

RDB 有兩種持久化方式:手動觸發自動觸發手動觸發使用以下兩個命令:

  • save:會阻塞當前 Redis 伺服器響應其他命令,直到 RDB 快照生成完成為止,對於記憶體 比較大的例項會造成長時間阻塞,所以線上環境不建議使用

  • bgsave:Redis 主程式會 fork 一個子程式,RDB 快照生成有子程式來負責,完成之後,子程式自動結束,bgsave 只會在 fork 子程式的時候短暫的阻塞,這個過程是非常短的,所以推薦使用該命令來手動觸發

除了執行命令手動觸發之外,Redis 內部還存在自動觸發 RDB 的持久化機制,在以下幾種情況下 Redis 會自動觸發 RDB 持久化

  • 在配置中配置了 save 相關配置資訊,如我們上面配置檔案中的 save 60 10000 ,也可以把它歸類為“save m n”格式的配置,表示 m 秒內資料集存在 n 次修改時,會自動觸發 bgsave。

  • 在主從情況下,如果從節點執行全量複製操作,主節點自動執行 bgsave 生成 RDB 檔案併傳送給從節點

  • 執行 debug reload 命令重新載入 Redis 時,也會自動觸發 save 操作

  • 預設情況下執行 shutdown 命令時,如果沒有開啟 AOF 持久化功能則自動執行 bgsave

上面就是 RDB 持久化的方式,可以看出 save 命令使用的比較少,大多數情況下使用的都是 bgsave 命令,所以這個 bgsave 命令還是有一些東西,那接下來我們就一起看看 bgsave 背後的原理,先從流程圖開始入手:

bgsave 運作流程圖

bgsave 命令大概有以下幾個步驟:

  • 1、執行 bgsave 命令,Redis 主程式判斷當前是否存在正在執行的 RDB/AOF 子程式,如果存在, bgsave 命令直接返回不在往下執行。
  • 2、父程式執行 fork 操作建立子程式,fork 操作過程中父程式會阻塞,fork 完成後父程式將不在阻塞可以接受其他命令。
  • 3、子程式建立新的 RDB 檔案,基於父程式當前記憶體資料生成臨時快照檔案,完成後用新的 RDB 檔案替換原有的 RDB 檔案,並且給父程式傳送 RDB 快照生成完畢通知

上面就是 bgsave 命令背後的一些內容,RDB 的內容就差不多了,我們一起來總結 RDB 持久化的優缺點,RDB 方式的優點

  • RDB 快照是某一時刻 Redis 節點記憶體資料,非常適合做備份,上傳到遠端伺服器或者檔案系統中,用於容災備份
  • 資料恢復時 RDB 要遠遠快於 AOF

有優點同樣存在缺點,RDB 的缺點有

  • RDB 持久化方式資料沒辦法做到實時持久化/秒級持久化。我們已經知道了 bgsave 命令每次執行都要執行 fork 操作建立子程式,屬於重量級操作,頻繁執行成本過高。
  • RDB 檔案使用特定二進位制格式儲存,Redis 版本演進過程中有多個格式 的 RDB 版本,存在老版本 Redis 服務無法相容新版 RDB 格式的問題

如果我們對資料要求比較高,每一秒的資料都不能丟,RDB 持久化方式肯定是不能夠滿足要求的,那 Redis 有沒有辦法滿足呢,答案是有的,那就是接下來的 AOF 持久化方式

AOF 持久化方式

Redis 預設並沒有開啟 AOF 持久化方式,需要我們自行開啟,在 redis.conf 配置檔案中將 appendonly no 調整為 appendonly yes,這樣就開啟了 AOF 持久化,與 RDB 不同的是 AOF 是以記錄操作命令的形式來持久化資料的,我們可以檢視以下 AOF 的持久化檔案 appendonly.aof

*2
$6
SELECT
$1
0
*3
$3
set
$6
mykey1
$6
你好
*3
$3
set
$4
key2
$5
hello
*1
$8
複製程式碼

大概就是長這樣的,具體的你可以檢視你 Redis 伺服器上的 appendonly.aof 配置檔案,這也意味著我們可以在 appendonly.aof 檔案中國修改值,等 Redis 重啟時將會載入修改之後的值。看似一些簡單的操作命令,其實從命令到 appendonly.aof 這個過程中非常有學問的,下面時 AOF 持久化流程圖:

AOF 持久化流程圖

在 AOF 持久化過程中有兩個非常重要的操作:一個是將操作命令追加到 AOF_BUF 快取區,另一個是 AOF_buf 快取區資料同步到 AOF 檔案,接下來我們詳細聊一聊這兩個操作:

1、為什麼要將命令寫入到 aof_buf 快取區而不是直接寫入到 aof 檔案?

我們知道 Redis 是單執行緒響應,如果每次寫入 AOF 命令都直接追加到磁碟上的 AOF 檔案中,這樣頻繁的 IO 開銷,Redis 的效能就完成取決於你的機器硬體了,為了提升 Redis 的響應效率就新增了一層 aof_buf 快取層, 利用的是作業系統的 cache 技術,這樣就提升了 Redis 的效能,雖然這樣效能是解決了,但是同時也引入了一個問題,aof_buf 快取區資料如何同步到 AOF 檔案呢?由誰同步呢?這就是我們接下來要聊的一個操作:fsync 操作

2、aof_buf 快取區資料如何同步到 aof 檔案中?

aof_buf 快取區資料寫入到 aof 檔案是有 linux 系統去完成的,由於 Linux 系統排程機制週期比較長,如果系統故障當機了,意味著一個週期內的資料將全部丟失,這不是我們想要的,所以 Linux 提供了一個 fsync 命令,fsync 是針對單個檔案操作(比如這裡的 AOF 檔案),做強制硬碟同步,fsync 將阻塞直到寫入硬碟完成後返回,保證了資料持久化,正是由於有這個命令,所以 redis 提供了配置項讓我們自行決定何時進行磁碟同步,redis 在 redis.conf 中提供了appendfsync 配置項,有如下三個選項:

# appendfsync always
appendfsync everysec
# appendfsync no
複製程式碼
  • always:每次有寫入命令都進行快取區與磁碟資料同步,這樣保證不會有資料丟失,但是這樣會導致 redis 的吞吐量大大下降,下降到每秒只能支援幾百的 TPS ,這違背了 redis 的設計,所以不推薦使用這種方式
  • everysec:這是 redis 預設的同步機制,雖然每秒同步一次資料,看上去時間也很快的,但是它對 redis 的吞吐量沒有任何影響,每秒同步一次的話意味著最壞的情況下我們只會丟失 1 秒的資料, 推薦使用這種同步機制,兼顧效能和資料安全
  • no:不做任何處理,快取區與 aof 檔案同步交給系統去排程,作業系統同步排程的週期不固定,最長會有 30 秒的間隔,這樣出故障了就會丟失比較多的資料。

這就是三種磁碟同步策略,但是你有沒有注意到一個問題,AOF 檔案都是追加的,隨著伺服器的執行 AOF 檔案會越來越大,體積過大的 AOF 檔案對 redis 伺服器甚至是主機都會有影響,而且在 Redis 重啟時載入過大的 AOF 檔案需要過多的時間,這些都是不友好的,那 Redis 是如何解決這個問題的呢?Redis 引入了重寫機制來解決 AOF 檔案過大的問題。

3、Redis 是如何進行 AOF 檔案重寫的?

Redis AOF 檔案重寫是把 Redis 程式內的資料轉化為寫命令同步到新 AOF 檔案的過程,重寫之後的 AOF 檔案會比舊的 AOF 檔案佔更小的體積,這是由以下幾個原因導致的:

  • 程式內已經超時的資料不再寫入檔案
  • 舊的 AOF 檔案含有無效命令,如 del key1、hdel key2、srem keys、set a111、set a222等。重寫使用程式內資料直接生成,這樣新的AOF檔案只保 留最終資料的寫入命令
  • 多條寫命令可以合併為一個,如:lpush list a、lpush list b、lpush list c可以轉化為:lpush list a b c。為了防止單條命令過大造成客戶端緩衝區溢 出,對於 list、set、hash、zset 等型別操作,以 64 個元素為界拆分為多條。

重寫之後的 AOF 檔案體積更小了,不但能夠節約磁碟空間,更重要的是在 Redis 資料恢復時,更小體積的 AOF 檔案載入時間更短。AOF 檔案重寫跟 RDB 持久化一樣分為手動觸發自動觸發,手動觸發直接呼叫 bgrewriteaof 命令就好了,我們後面會詳細聊一聊這個命令,自動觸發就需要我們在 redis.conf 中修改以下幾個配置

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
複製程式碼
  • auto-aof-rewrite-percentage:代表當前 AOF檔案空間 (aof_current_size)和上一次重寫後 AOF 檔案空間(aof_base_size)的比值,預設是 100%,也就是一樣大的時候
  • auto-aof-rewrite-min-size:表示執行 AOF 重寫時 AOF 檔案最小體積,預設為 64MB,也就是說 AOF 檔案最小為 64MB 才有可能觸發重寫

滿足了這兩個條件,Redis 就會自動觸發 AOF 檔案重寫,AOF 檔案重寫的細節跟 RDB 持久化生成快照有點類似,下面是 AOF 檔案重寫流程圖:

AOF 檔案重寫

AOF 檔案重寫也是交給子程式來完成,跟 RDB 生成快照很像,AOF 檔案重寫在重寫期間建立了一個 aof_rewrite_buf 快取區來儲存重寫期間主程式響應的命令,等新的 AOF 檔案重寫完成後,將這部分檔案同步到新的 AOF 檔案中,最後用新的 AOF 檔案替換掉舊的 AOF 檔案。需要注意的是在重寫期間,舊的 AOF 檔案依然會進行磁碟同步,這樣做的目的是防止重寫失敗導致資料丟失,

Redis 持久化資料恢復

我們知道 Redis 是基於記憶體的,所有的資料都存放在記憶體中,由於機器當機或者其他因素重啟了就會導致我們的資料全部丟失,這也就是要做持久化的原因,當伺服器重啟時,Redis 會從持久化檔案中載入資料,這樣我們的資料就恢復到了重啟前的資料,在資料恢復這一塊Redis 是如何實現的?我們先來看看資料恢復的流程圖:

Redis 資料恢復

Redis 的資料恢復流程比較簡單,優先恢復的是 AOF 檔案,如果 AOF 檔案不存在時則嘗試載入 RDB 檔案,為什麼 RDB 的恢復速度比 AOF 檔案快,但是還是會優先載入 AOF 檔案呢?我個人認為是 AOF 檔案資料更全面並且 AOF 相容性比 RDB 強,需要注意的是當存在 RDB/AOF 時,如果資料載入不成功,Redis 服務啟動會失敗。

最後

目前網際網路上很多大佬都有 Redis 系列教程,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支援。若文中有所錯誤之處,還望提出,謝謝。

歡迎掃碼關注微信公眾號:「平頭哥的技術博文」,和平頭哥一起學習,一起進步。

平頭哥的技術博文

相關文章