天天看點

Redis 持久化政策淺析

Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的記憶體高速緩存資料存儲服務。使用 ANSI C 語言編寫,支援網絡、可基于記憶體亦可持久化的日志型、Key-Value 資料存儲,并提供多種語言的 API。

▶ 簡介

Redis 是記憶體資料庫,資料都是存儲在記憶體中,為了避免程序退出導緻資料的永久丢失,需要定期将 Redis 中的資料以某種形式(資料或指令)從記憶體儲存到硬碟。當下次 Redis 重新開機時,利用持久化檔案實作資料恢複。除此之外,為了進行災難備份,可以将持久化檔案拷貝到一個遠端位置。Redis 的持久化機制有兩種:

  • RDB(Redis Data Base) 記憶體快照
  • AOF(Append Only File) 增量日志

RDB 将目前資料儲存到硬碟,AOF 則是将每次執行的寫指令儲存到硬碟(類似于 MySQL 的 Binlog)。AOF 持久化的實時性更好,即當程序意外退出時丢失的資料更少。

▶ RDB 持久化

簡介

RDB ( Redis Data Base) 指的是在指定的時間間隔内将記憶體中的資料集快照寫入磁盤,RDB 是記憶體快照(記憶體資料的二進制序列化形式)的方式持久化,每次都是從 Redis 中生成一個快照進行資料的全量備份。

優點:

  • 存儲緊湊,節省記憶體空間。
  • 恢複速度非常快。
  • 适合全量備份、全量複制的場景,經常用于災難恢複(對資料的完整性和一緻性要求相對較低的場合)。

缺點:

  • 容易丢失資料,容易丢失兩次快照之間 Redis 伺服器中變化的資料。
  • RDB 通過 fork 子程序對記憶體快照進行全量備份,是一個重量級操作,頻繁執行成本高。
Redis 持久化政策淺析

RDB 檔案結構

在預設情況下,Redis 将資料庫快照儲存在名字為 dump.rdb 的二進制檔案中。RDB 檔案結構由五個部分組成:

(1)長度為5位元組的 ​

​REDIS​

​ 常量字元串。

(2)4位元組的 db_version,辨別 RDB 檔案版本。

(3)databases:不定長度,包含零個或多個資料庫,以及各資料庫中的鍵值對資料。

(4)1位元組的 EOF 常量,表示檔案正文内容結束。

(5)check_sum: 8位元組長的無符号整數,儲存校驗和。

Redis 持久化政策淺析

資料結構舉例,以下是資料庫[0]和資料庫[3]有資料的情況:

Redis 持久化政策淺析

RDB 檔案的建立

手動指令觸發

手動觸發 RDB 持久化的方式可以使用 ​

​save​

​​ 指令和 ​

​bgsave​

​ 指令,這兩個指令的差別如下:

​save​

​​:執行​

​ save ​

​指令,阻塞 Redis 的其他操作,會導緻 Redis 無法響應用戶端請求,不建議使用。

​bgsave​

​​:執行 ​

​bgsave​

​ 指令,Redis 背景建立子程序,異步進行快照的儲存操作,此時 Redis 仍然能響應用戶端的請求。

自動間隔性儲存

在預設情況下,Redis 将資料庫快照儲存在名字為 dump.rdb的二進制檔案中。可以對 Redis 進行設定,讓它在“ N 秒内資料集至少有 M 個改動”這一條件被滿足時,自動儲存一次資料集。

比如說, 以下設定會讓 Redis 在滿足“ 60 秒内有至少有 10 個鍵被改動”這一條件時,自動儲存一次資料集:​

​save 60 10​

​。

Redis 的預設配置如下,三個設定滿足其一即可觸發自動儲存:

save 60 10000
save 300 10
save 900 1      

自動儲存配置的資料結構

記錄了伺服器觸發自動 ​

​BGSAVE​

​​ 條件的​

​saveparams​

​屬性。

​lastsave​

​​ 屬性:記錄伺服器最後一次執行 ​

​SAVE​

​​ 或者 ​

​BGSAVE​

​ 的時間。

​dirty​

​ 屬性:以及自最後一次儲存 RDB 檔案以來,伺服器進行了多少次寫入。

Redis 持久化政策淺析

備份過程

RDB 持久化方案進行備份時,Redis 會單獨 fork 一個子程序來進行持久化,會将資料寫入一個臨時檔案中,持久化完成後替換舊的 RDB 檔案。在整個持久化過程中,主程序(為用戶端提供服務的程序)不參與 IO 操作,這樣能確定 Redis 服務的高性能,RDB 持久化機制适合對資料完整性要求不高但追求高效恢複的使用場景。下面展示 RDB 持久化流程:

Redis 持久化政策淺析

關鍵執行步驟如下

  1. Redis 父程序首先判斷:目前是否在執行 save,或 bgsave/bgrewriteaof 的子程序,如果在執行則 bgsave 指令直接傳回。bgsave/bgrewriteaof 的子程序不能同時執行,主要是基于性能方面的考慮:兩個并發的子程序同時執行大量的磁盤寫操作,可能引起嚴重的性能問題。
  2. 父程序執行 fork 操作建立子程序,這個過程中父程序是阻塞的,Redis 不能執行來自用戶端的任何指令。父程序 fork 後,bgsave 指令傳回”Background saving started”資訊并不再阻塞父程序,并可以響應其他指令。
  3. 子程序程序對記憶體資料生成快照檔案。
  4. 父程序在此期間接收的新的寫操作,使用 COW 機制寫入。
  5. 子程序完成快照寫入,替換舊 RDB 檔案,随後子程序退出。
在生成 RDB 檔案的步驟中,在同步到磁盤和持續寫入這個過程是如何處理資料不一緻的情況呢?生成快照 RDB 檔案時是否會對業務産生影響?

Fork 子程序的作用

上面說到了 RDB 持久化過程中,主程序會 fork 一個子程序來負責 RDB 的備份,這裡簡單介紹一下 fork:

  • Linux 作業系統中的程式,fork 會産生一個和父程序完全相同的子程序。子程序與父程序所有的資料均一緻,但是子程序是一個全新的程序,與原程序是父子程序關系。
  • 出于效率考慮,Linux 作業系統中使用 COW(Copy On Write)寫時複制機制,fork 子程序一般情況下與父程序共同使用一段實體記憶體,隻有在程序空間中的記憶體發生修改時,記憶體空間才會複制一份出來。

在 Redis 中,RDB 持久化就是充分的利用了這項技術,Redis 在持久化時調用 glibc 函數 fork 一個子程序,全權負責持久化工作,這樣父程序仍然能繼續給用戶端提供服務。fork 的子程序初始時與父程序(Redis 的主程序)共享同一塊記憶體;當持久化過程中,用戶端的請求對記憶體中的資料進行修改,此時就會通過 COW (Copy On Write) 機制對資料段頁面進行分離,也就是複制一塊記憶體出來給主程序去修改。

Redis 持久化政策淺析

通過 fork 建立的子程序能夠獲得和父程序完全相同的記憶體空間,父程序對記憶體的修改對于子程序是不可見的,兩者不會互相影響;

通過 fork 建立子程序時不會立刻觸發大量記憶體的拷貝,采用的是寫時拷貝 COW (Copy On Write)。核心隻為新生成的子程序建立虛拟空間結構,它們來複制于父程序的虛拟究竟結構,但是不為這些段配置設定實體記憶體,它們共享父程序的實體空間,當父子程序中有更改相應段的行為發生時,再為子程序相應的段配置設定實體空間;

▶ AOF 持久化

簡介

AOF (Append Only File) 是把所有對記憶體進行修改的指令(寫操作)以獨立日志檔案的方式進行記錄,重新開機時通過執行 AOF 檔案中的 Redis 指令來恢複資料。類似MySql bin-log 原理。AOF 能夠解決資料持久化實時性問題,是現在 Redis 持久化機制中主流的持久化方案。

Redis 持久化政策淺析

優點:

  • 資料的備份更加完整,丢失資料的機率更低,适合對資料完整性要求高的場景
  • 日志檔案可讀,AOF 可操作性更強,可通過記錄檔檔案進行修複

缺點:

  • AOF 日志記錄在長期運作中逐漸龐大,恢複起來非常耗時,需要定期對 AOF 日志進行瘦身處理
  • 恢複備份速度比較慢
  • 同步寫操作頻繁會帶來性能壓力

AOF 檔案内容

被寫入 AOF 檔案的所有指令都是以 RESP 格式儲存的,是純文字格式儲存在 AOF 檔案中。

Redis 用戶端和服務端之間使用一種名為 ​

​RESP(REdis Serialization Protocol)​

​ 的二進制安全文本協定進行通信。

下面以一個簡單的 SET 指令進行舉例:

redis> SET mykey "hello"    //用戶端指令
OK      

用戶端封裝為以下格式(每行用​

​ \r\n​

​分隔)

*3
$3
SET
$5
mykey
$5
hello      

AOF 檔案中記錄的文本内容如下

*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n       //多出一個SELECT 0 指令,用于指定資料庫,為系統自動添加
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nhello\r\n      

AOF 持久化實作

AOF 持久化方案進行備份時,用戶端所有請求的寫指令都會被追加到 AOF 緩沖區中,緩沖區中的資料會根據 Redis 配置檔案中配置的同步政策來同步到磁盤上的 AOF 檔案中,追加儲存每次寫的操作到檔案末尾。同時當 AOF 的檔案達到重寫政策配置的門檻值時,Redis 會對 AOF 日志檔案進行重寫,給 AOF 日志檔案瘦身。Redis 服務重新開機的時候,通過加載 AOF 日志檔案來恢複資料。

Redis 持久化政策淺析

AOF 的執行流程包括:

指令追加(append)

Redis 先将寫指令追加到緩沖區 aof_buf,而不是直接寫入檔案,主要是為了避免每次有寫指令都直接寫入硬碟,導緻硬碟 IO 成為 Redis 負載的瓶頸。

struct redisServer {
   //其他域...
   sds  aof_buf;           // sds類似于Java中的String
   //其他域...
}      

檔案寫入(write)和檔案同步(sync)

根據不同的同步政策将 aof_buf 中的内容同步到硬碟;

Linux 作業系統中為了提升性能,使用了頁緩存(page cache)。當我們将 aof_buf 的内容寫到磁盤上時,此時資料并沒有真正的落盤,而是在 page cache 中,為了将 page cache 中的資料真正落盤,需要執行 fsync / fdatasync 指令來強制刷盤。這邊的檔案同步做的就是刷盤操作,或者叫檔案刷盤可能更容易了解一些。

AOF 緩存區的同步檔案政策由參數 appendfsync 控制,有三種同步政策,各個值的含義如下:

  • ​always​

    ​:指令寫入 aof_buf 後立即調用系統 write 操作和系統 fsync 操作同步到 AOF 檔案,fsync 完成後線程傳回。這種情況下,每次有寫指令都要同步到 AOF 檔案,硬碟 IO 成為性能瓶頸,Redis 隻能支援大約幾百TPS寫入,嚴重降低了 Redis 的性能;即便是使用固态硬碟(SSD),每秒大約也隻能處理幾萬個指令,而且會大大降低 SSD 的壽命。可靠性較高,資料基本不丢失。
  • ​no​

    ​:指令寫入 aof_buf 後調用系統 write 操作,不對 AOF 檔案做 fsync 同步;同步由作業系統負責,通常同步周期為30秒。這種情況下,檔案同步的時間不可控,且緩沖區中堆積的資料會很多,資料安全性無法保證。
  • ​everysec​

    ​:指令寫入 aof_buf 後調用系統 write 操作,write 完成後線程傳回;fsync 同步檔案操作由專門的線程每秒調用一次。everysec 是前述兩種政策的折中,是性能和資料安全性的平衡,是以是 Redis 的預設配置,也是我們推薦的配置。

檔案重寫(rewrite)

定期重寫 AOF 檔案,達到壓縮的目的。

AOF 重寫是 AOF 持久化的一個機制,用來壓縮 AOF 檔案,通過 fork 一個子程序,重新寫一個新的 AOF 檔案,該次重寫不是讀取舊的 AOF 檔案進行複制,而是讀取記憶體中的Redis資料庫,重寫一份 AOF 檔案,有點類似于 RDB 的快照方式。

檔案重寫之是以能夠壓縮 AOF 檔案,原因在于:

  • 過期的資料不再寫入檔案
  • 無效的指令不再寫入檔案:如有些資料被重複設值(set mykey v1, set mykey v2)、有些資料被删除了(sadd myset v1, del myset)等等
  • 多條指令可以合并為一個:如 sadd myset v1, sadd myset v2, sadd myset v3 可以合并為 sadd myset v1 v2 v3。不過為了防止單條指令過大造成用戶端緩沖區溢出,對于 list、set、hash、zset類型的 key,并不一定隻使用一條指令;而是以某個常量為界将指令拆分為多條。這個常量在 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD 中定義,不可更改,2.9版本中值是64。

AOF 重寫

前面提到 AOF 的缺點時,說過 AOF 屬于日志追加的形式來存儲 Redis 的寫指令,這會導緻大量備援的指令存儲,進而使得 AOF 日志檔案非常龐大,比如同一個 key 被寫了 10000 次,最後卻被删除了,這種情況不僅占記憶體,也會導緻恢複的時候非常緩慢,是以 Redis 提供重寫機制來解決這個問題。Redis 的 AOF 持久化機制執行重寫後,儲存的隻是恢複資料的最小指令集,我們如果想手動觸發可以使用如下指令:

bgrewriteaof      

檔案重寫時機

相關參數:

  • aof_current_size:表示目前 AOF 檔案空間
  • aof_base_size:表示上一次重寫後 AOF 檔案空間
  • auto-aof-rewrite-min-size: 表示運作 AOF 重寫時檔案的最小體積,預設為64MB
  • auto-aof-rewrite-percentage: 表示目前 AOF 重寫時檔案空間(aof_current_size)超過上一次重寫後 AOF 檔案空間(aof_base_size)的比值多少後會重寫。

同時滿足下面兩個條件,則觸發 AOF 重寫機制:

  • aof_current_size 大于 auto-aof-rewrite-min-size
  • 目前 AOF 相比上一次 AOF 的增長率:(aof_current_size - aof_base_size)/aof_base_size 大于或等于 auto-aof-rewrite-percentage

AOF 重寫流程如下:

  1. bgrewriteaof 觸發重寫,判斷是否存在 bgsave 或者 bgrewriteaof 正在執行,存在則等待其執行結束再執行
  2. 主程序 fork 子程序,防止主程序阻塞無法提供服務,類似 RDB
  3. 子程序周遊 Redis 記憶體快照中資料寫入臨時 AOF 檔案,同時會将新的寫指令寫入 aof_buf 和 aof_rewrite_buf 兩個重寫緩沖區,前者是為了寫回舊的 AOF 檔案,後者是為了後續重新整理到臨時 AOF 檔案中,防止快照記憶體周遊時新的寫入操作丢失
  4. 子程序結束臨時 AOF 檔案寫入後,通知主程序
  5. 主程序會将上面 3 中的 aof_rewirte_buf 緩沖區中的資料寫入到子程序生成的臨時 AOF 檔案中
  6. 主程序使用臨時 AOF 檔案替換舊 AOF 檔案,完成整個重寫過程。
Redis 持久化政策淺析

在實際中,為了避免在執行指令時造成用戶端輸入緩沖區溢出,重寫程式會檢查集合元素數量是否超過 REDIS_AOF_REWRITE_ITEMS_PER_CMD 常量的值,如果超過了,則會使用多個指令來記錄,而不單單使用一條指令。

Redis 2.9版本中該常量為64,如果一個指令的集合鍵包含超過了64個元素,重寫程式會拆成多個指令。

SADD <key> <elem1> <elem2>...<elem64>

SADD <key> <elem65> <elem66>...<elem128>

AOF重寫是一個有歧義的名字,該功能是通過直接讀取資料庫的鍵值對實作的,程式無需對現有AOF檔案進行任何讀入、分析或者寫入操作。

▶ 思考

AOF 與 WAL

Redis 為什麼考慮使用 AOF 而不是 WAL 呢?

很多資料庫都是采用的 Write Ahead Log(WAL)寫前日志,其特點就是先把修改的資料記錄到日志中,再進行寫資料的送出,可以友善通過日志進行資料恢複。

但是 Redis 采用的卻是 AOF(Append Only File)寫後日志,特點就是先執行寫指令,把資料寫入記憶體中,再記錄日志。

如果先讓系統執行指令,隻有指令能執行成功,才會被記錄到日志中。是以,Redis 使用寫後日志這種形式,可以避免出現記錄錯誤指令的情況。

另外還有一個原因就是:AOF 是在指令執行後才記錄日志,是以不會阻塞目前的寫操作。

AOF 和 RDB 之間的互相作用

在版本号大于等于2.4的 Redis 中,BGSAVE 執行的過程中,不可以執行 BGREWRITEAOF。反過來說,在 BGREWRITEAOF 執行的過程中,也不可以執行 BGSAVE。這可以防止兩個 Redis 背景程序同時對磁盤進行大量的 I/O 操作。

如果 BGSAVE 正在執行,并且使用者顯示地調用 BGREWRITEAOF 指令,那麼伺服器将向使用者回複一個 OK 狀态,并告知使用者,BGREWRITEAOF 已經被預定執行:一旦 BGSAVE 執行完畢 BGREWRITEAOF 就會正式開始。

當 Redis 啟動時,如果 RDB 持久化和 AOF 持久化都被打開了,那麼程式會優先使用 AOF 檔案來恢複資料集,因為 AOF 檔案所儲存的資料通常是最完整的。

▶ 混合持久化

Redis4.0 後大部分的使用場景都不會單獨使用 RDB 或者 AOF 來做持久化機制,而是兼顧二者的優勢混合使用。其原因是 RDB 雖然快,但是會丢失比較多的資料,不能保證資料完整性;AOF 雖然能盡可能保證資料完整性,但是性能确實是一個诟病,比如重放恢複資料。

Redis從4.0版本開始引入 RDB-AOF 混合持久化模式,這種模式是基于 AOF 持久化模式建構而來的,混合持久化通過 ​

​aof-use-rdb-preamble yes​

​ 開啟。

那麼 Redis 伺服器在執行 AOF 重寫操作時,就會像執行 BGSAVE 指令那樣,根據資料庫目前的狀态生成出相應的 RDB 資料,并将這些資料寫入建立的 AOF 檔案中,至于那些在 AOF 重寫開始之後執行的 Redis 指令,則會繼續以協定文本的方式追加到新 AOF 檔案的末尾,即已有的 RDB 資料的後面。

換句話說,在開啟了 RDB-AOF 混合持久化功能之後,伺服器生成的 AOF 檔案将由兩個部分組成,其中位于 AOF 檔案開頭的是 RDB 格式的資料,而跟在 RDB 資料後面的則是 AOF 格式的資料。

當一個支援 RDB-AOF 混合持久化模式的 Redis 伺服器啟動并載入 AOF 檔案時,它會檢查 AOF 檔案的開頭是否包含了 RDB 格式的内容。

  • 如果包含,那麼伺服器就會先載入開頭的 RDB 資料,然後再載入之後的 AOF 資料。
  • 如果 AOF 檔案隻包含 AOF 資料,那麼伺服器将直接載入 AOF 資料。

其日志檔案結構如下:

Redis 持久化政策淺析

▶ 總結

  • 推薦是兩者均開啟。
  • 如果對資料不敏感,可以選單獨用 RDB。
  • 如果隻是做純記憶體緩存,可以都不用。

繼續閱讀