Redis持久化方式RDB和AOF詳解


Redis作為基於內存的NoSQL數據庫,在保障高性能、高並發的同時也支持數據持久化,將內存中的數據寫入磁盤中,防止突發場景(如斷電)導致的數據丟失。Redis提供了RDB和AOF兩種持久化方案。

RDB

RDB全稱Redis DataBase,在指定時間間隔內將內存中的數據集快照進行持久化。是Redis默認啟用的持久化方案,持久化過程會生成一個壓縮過的二進制文件,默認名稱為dump.rdb,當服務器重啟時,加載持久化文件恢復數據到內存中。
未命名文件.svg
Redis沒有提供特定的加載方式,當服務器在啟動時候只要發現存在RDB文件就會自動載入,載入過程服務器處於阻塞狀態,直到RDB文件加載完成,服務器開始接收客戶端請求。

2022 11:26:14.942 * DB loaded from append only file: 0.000 seconds
2022 11:26:14.942 * Ready to accept connections

RDB文件創建

RDB有兩種觸發方式,通過客戶端發起SAVE或者BGSAVE手動觸發,或者通過配置文件配置,Redis服務器在啟動時加載配置信息,由事件來自動觸發RDB文件生成調度。

手動觸發

在Redis中可以使用SAVE或者BGSAVE命令來生成RDB文件,兩者區別在於:

  • SAVE:會阻塞主進程,直到RDB文件創建完成,在此期間服務器不再處理任何命令請求。
  • BGSAVE:不阻塞主進程,后台保存的同時服務器可以持續處理客戶端請求。

在執行BGSAVE過程中,服務端不會再接收SAVE或者BGSAVE命令,如果有BGREWRITEAOF命令發起,會被延遲到BGSAVE之后執行,BGREWRITEAOF在執行過程中,發起BGSAVE命令,會被拒絕執行。

自動觸發

  1. 參數配置

Redis在配置文件redis.conf中提供了save參數,只要滿足其中的任一條件,就會觸發BGSAVE操作。

# Unless specified otherwise, by default Redis will save the DB:
#   * After 3600 seconds (an hour) if at least 1 key changed
#   * After 300 seconds (5 minutes) if at least 100 keys changed
#   * After 60 seconds if at least 10000 keys changed
#
# You can set these explicitly by uncommenting the three following lines.
#
# save 3600 1 表示在3600秒內,至少有一個key被修改,BGSAVE就會執行
# save 300 100
# save 60 10000

修改redis.conf中的配置為save 10 ,在10秒內只要有一個key被修改就觸發BGSAVE。查看Redis的日志輸出:

18:C 20 Mar 2022 11:16:05.072 * DB saved on disk
18:C 20 Mar 2022 11:16:05.072 * RDB: 0 MB of memory used by copy-on-write
1:M 20 Mar 2022 11:16:05.160 * Background saving terminated with success
1:M 20 Mar 2022 11:16:32.518 * 1 changes in 10 seconds. Saving...
1:M 20 Mar 2022 11:16:32.519 * Background saving started by pid 20
20:C 20 Mar 2022 11:16:32.523 * DB saved on disk
20:C 20 Mar 2022 11:16:32.523 * RDB: 0 MB of memory used by copy-on-write
1:M 20 Mar 2022 11:16:32.619 * Background saving terminated with success
1:M 20 Mar 2022 11:16:43.043 * 1 changes in 10 seconds. Saving...
1:M 20 Mar 2022 11:16:43.043 * Background saving started by pid 21

在日志中可以看到,BGSAVE執行一次就會創建一個新的進程,並且用到了copy-on-write,這部分接下來會詳細描述其執行過程。

  1. 參數加載過程

Redis Server啟動時,會讀取配置文件中的save屬性值,將傳入的參數賦值給redisServer結構體的saveParams屬性。

struct redisServer {
    // ...
    struct saveparam *saveparams;//記錄了save保存條件的數組
    // ...
};

struct saveparam {
    time_t seconds;//秒數
    int changes;//修改數
};

此外服務器狀態還維持了一個dirty計數器和lastsave屬性。

  • dirty計數器:記錄距離上一次服務器成功執行SAVE或者BGSAVE后進行了多少次寫入、刪除、修改等操作。
  • lastsave:上次SAVE或者BGSAVE成功的時間。

Redis服務器中的周期函數serverCron默認100ms執行一次,用於服務器信息維護,其中有一項工作就是檢查save條件是否滿足,如果滿足就開始執行BGSAVE命令。

BGSAVE執行過程

Redis中RDB操作幾乎都是通過BGSAVE命令來觸發,SAVE的使用場景比較少見,因此BGSAVE的執行過程,對於一些問題的排查(如數據量大情況下CPU異常波動)就顯得至關重要。BGSAVE執行過程整如圖所示:
未命名文件 (1).svg
BGSAVE命令開始執行,父進程會fork一個子進程,由子進程來完成數據持久化操作,父進程繼續處理來自客戶端的請求,是在fork的過程會阻塞,這個過程耗時長短取決於數據量的多少,具體流程如下:

  • 父進程通過fork來復制一個環境、變量等完全相同的子進程
  • 當復制完成后,子進程通過信號通知父進程,子進程開始將數據集寫入一個臨時的RDB文件,當新的RDB文件寫入完成后會替換掉原來的RDB文件。
  • fork的過程如果出現錯誤將會交由父進程處理。

在Linux中fork是重量級調用,因為其建立了父進程完整的副本,為了減少相關工作量,使用到了COW(Copy On Write)寫時復制技術,整個過程如圖所示:
未命名文件 (4).svg
寫時復制適合的場景是讀多寫少的場景,比如黑白名單,熱點數據等。這樣做的好處是避免了父進程完整的數據復制到子進程,如果采用直接復制的辦法,也就意味着執行這個調用至少需要分配2倍的內存空間,對資源大大浪費,而且復制的過程在數據量比較大的時候也會阻塞較長一段時間。

COW機制使得內核盡可能延遲頁的復制,最理想的情況就是完全不需要復制,不使用任何額外的內存空間。在沒有數據修改時共享數據,當數據發生變化修改時才進行復制。

  • 進程通常只使用了內存頁的一小部分,在調用fork的過程中,子進程並不復制整個內存空間,而是只復制其內存頁,建立虛擬內存空間和物理內存頁之間的聯系,fork之后父子進程的地址空間指向同樣的物理內存頁,並且父子進程不能修改彼此的內存頁,被標記為只讀。
  • 父進程發生寫操作時,因為權限已經設置為read-only了,所以會觸發頁異常中斷(page-fault)。在中斷處理中,需要被寫入的內存頁面會復制一份,復制出來的舊數據交給子進程使用,然后主進程繼續處理請求。

RDB文件格式

RDB文件是一個二進制文件,有着特定的文件結構。Redis中數據對象存儲了長度信息,在讀寫之前就可以知道需要占用的空間大小,並且使用LZF 算法來壓縮文件大小,RDB文件被優化的適合快速的讀寫。優化快速讀寫意味着磁盤格式應盡可能接近內存中表示形式,下面展示了RDB文件的基本格式:

----------------------------# RDB is a binary format. There are no new lines or spaces in the file.
52 45 44 49 53              # Magic String "REDIS"
30 30 30 37                 # 4 digit ASCCII RDB Version Number. In this case, version = "0007" = 7
----------------------------
FE 00                       # FE = code that indicates database selector. db number = 00
----------------------------# Key-Value pair starts
FD $unsigned int            # FD indicates "expiry time in seconds". After that, expiry time is read as a 4 byte unsigned int
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
FC $unsigned long           # FC indicates "expiry time in ms". After that, expiry time is read as a 8 byte unsigned long
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
$value-type                 # This key value pair doesn't have an expiry. $value_type guaranteed != to FD, FC, FE and FF
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding         # Previous db ends, next db starts. Database number read using length encoding.
----------------------------
...                         # Key value pairs for this database, additonal database
                            
FF                          ## End of RDB file indicator
8 byte checksum             ## CRC 64 checksum of the entire file.

接下來就進步的對RDB文件中的存儲格式進一步按照層級分析。

  1. RDB存儲格式

首先了解RDB文件的整體存儲結構:
未命名文件.svg

  • REDIS:Magic(魔數)占據五個字節,標志一份文件是否為RDB文件,類似於Java字節碼文件的cafebabe標識。
  • db_version:長度4個字節,值為字符串記錄的整數,記錄RDB文件的版本號。
  • databses:這部分是數據文件存放區域,包含一個或多個數據庫。
  • EOF:長度為1個字節,標志着RDB文件正文內容的結束,讀取到此處標志鍵值對已經被恢復。
  • check_sum:長度為8字節的無符號整數,保存着根據前面幾部分計算得出的校驗和,用來檢查RDB文件是否損壞。
  1. databases存儲格式

databases部分可以保存任意多個非空數據庫,每個數據庫中的文件按照固定的數據格式來保存,如下所示:
未命名文件.svg

  • SELECTDB:長度為1個字節,當讀取到這個標志時,意味着接下來讀取的是一個數據庫編號。
  • db_number:數據庫的編號,根據號碼大小這部分的長度可以動態調整,可以是1、2、5字節的長度。
  • key_value_pairs:保存了數據庫中所有的鍵值對信息。
  1. key_value_pairs存儲格式

key_value_pairs的存儲根據實際情況有兩種類型,過期時間也被包含在鍵值對中,除此之外,鍵值對中還包含了數據類型信息,每一個鍵值對一般包含四部分信息,其中過期時間時可選項。

  • 不包含過期時間的鍵值對

未命名文件 (1).svg

  • 包含過期時間的鍵值對

未命名文件 (2).svg
區別就在於過期鍵值對包含了以ms為單位的過期時間信息,其中各部分表示的信息如下:

  • TYPE:長度為1字節,記錄了value的類型,Redis讀取RDB文件的鍵值對時會根據TYPE的類型來解釋鍵值對信息,Redis中定義了以下集中TYPE的值:
    • REDIS_RDB_TYPE_STRING
    • REDIS_RDB_TYPE_LIST
    • REDIS_RDB_TYPE_SET
    • REDIS_RDB_TYPE_ZSET
    • REDIS_RDB_TYPE_HASH
    • REDIS_RDB_TYPE_LIST_ZIPLIST
    • REDIS_RDB_TYPE_SET_INTSET
    • REDIS_RDB_TYPE_ZSET_ZIPLIST
    • REDIS_RDB_TYPE_HASH_ZIPLIST
  • key:是一個字符串對象,編碼格式與"String Encoding"類型一致
  • value:key對應的鍵值對的值,根據其保存數據類型的不同,value的格式也不一樣
  • EXPIRETIME_MS:長度為1字節的標志位,當讀取到此處時,告知接下來讀取的數據為以ms為過期時間的值。
  • ms:長度為8字節的過期時間值,可以為負數。
  1. value編碼類型

value部分保存了一個值對象,每個值對象保存的數據都與TYPE記錄的類型所對應。因此value的編碼格式根據value的TYPE,一個字節標志指示用於保存 Value 的編碼。

  • 0 = "String Encoding"
  • 1 = "List Encoding"
  • 2 = "Set Encoding"
  • 3 = "Sorted Set Encoding"
  • 4 = "Hash Encoding"
  • 9 = "Zipmap Encoding"
  • 10 = "Ziplist Encoding"
  • 11 = "Intset Encoding"
  • 12 = "Sorted Set in Ziplist Encoding"
  • 13 = "Hashmap in Ziplist Encoding" (Introduced in rdb version 4)

下面對Redis中不同對象類型的編碼進行簡單分析:

  • Length Encoding

每個對象中都存儲了占用長度,長度編碼用於存儲流中下一個對象的長度,長度編碼是一種可變字節編碼,旨在使用盡可能少的字節。長度編碼的過程如下:

  • 從流中讀取一個字節,讀取兩個最高有效位。
  • 如果起始位為 00,則接下來的 6 位表示長度
  • 如果起始位為 01,則從流中讀取一個額外的字節。組合的 14 位表示長度。
  • 如果起始位為 10,則丟棄剩余的 6 位。從流中讀取額外的4個字節,這4個字節表示長度(在RDB版本6中以大端格式)
  • 如果起始位為 11,則下一個對象將以特殊格式編碼。其余 6 位指示格式。此編碼通常用於將數字存儲為字符串,或存儲編碼的字符串。請參閱字符串編碼

儲存長度時,由於這種編碼方式:

  • 最多 63 個(包括 63 個)的數字可以存儲在 1 個字節中。
  • 最多 16383 的數字(包括 16383)可以存儲在 2 個字節中。
  • 最多 2^32 -1 的數字可以存儲在 5 個字節中。
  • String Encoding

Redis字符串是二進制安全,這意味着你可以存放任意數據,字符串沒有任何的特殊字符串作為結尾標記,最好將Redis的字符串看作一個字節數組。Redis有三種String類型:

  • Length Prefixed String:長度前綴字符串非常簡單。字符串的長度(以字節為單位)首先使用"Length Encoding"進行編碼。在此之后,將存儲字符串的原始字節。
  • An 8, 16 or 32 bit integer(一個8bit、16bit或者64bit所表示的整數):首先閱讀"Length Encoding"部分,具體是前兩位為11時的部分。在這種情況下,讀取剩余的6位。
    • 0 表示后面跟着一個 8 位整數
    • 1 表示后面跟着一個 16 位整數
    • 2 表示后面跟着一個 32 位整數
  • A LZF compressed string(使用LZF壓縮的字符串):首先閱讀"Length Encoding"部分,特別是前兩位為11的部分。在這種情況下,將讀取剩余的 6 位。如果這 6 位的值為 4,則表示后面跟着壓縮字符串。
    • 使用"長度編碼"從流中讀取壓縮的長度 clen
    • 使用"長度編碼"從流中讀取未壓縮的長度
    • 從流中讀取下一個 clen 字節
    • 最后,使用LZF算法解壓縮這些字節
  • List Encoding

Redis中List使用一些列的String類型來表示,進行數據存儲

  • 首先,使用"Length Encoding"從流中讀取列表大小的大小
  • 接下來,使用"字符串編碼"從流中讀取大小字符串
  • 然后使用這些字符串重新構造列表
  • Set Encoding

Set集合的編碼與List列表完全相同。

  • Hash Encoding
    • 首先,使用"Length Encoding"從流中讀取哈希大小的大小。
    • 接下來,使用"字符串編碼"從流中讀取2 * 大小的字符串,備用字符串是鍵和值。

示例:讀取 2 us washington india delhi,對應如下格式:

{"us" => "washington", "india" => "delhi"}

上面簡單介紹了幾種對象的編碼方式,更多編碼相關內容,查看https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format

AOF

AOF全稱Append Only File,AOF通過特定條件觸發服務器寫操作追加進行內存數據狀態持久化。既然已經有了RDB為什么還要有AOF,上面已經說明RDB保存的是某個時刻的數據快照,在子進程保存過程中父進程新接收的請求增加或者修改的數據不會被處理,會造成一定的數據丟失,AOF 的主要作用是解決數據持久化的實時性和完整性問題。
fork.svg
AOF中存儲的內容為固定格式的文本文件,設置一個新的鍵值對:

127.0.0.1:6379> set aof testaof
OK

查看AOF文件內容:

root@starsray:/var/lib/redis# cat /var/lib/redis/appendonly.aof 
*2$6SELECT$10*3$3set$3aof$7testaof

AOF創建與還原

  1. AOF功能開啟

AOF的開啟通過redis.conf的配置項來配置開啟

# yes 開啟 no 關閉
appendonly yes

# AOF文件的名稱
appendfilename "appendonly.aof"

AOF持久化的過程主要包含命令追加(append)、文件寫入和文件同步(sync)的過程。這里需要明確文件寫入和文件同步的概念:

為了提高文件的寫入效率,在現代操作系統中,當用戶調用write函數,將一些數據寫入到文件的時候,os通常會將寫入數據暫時保存在一個內存緩沖區里面(例如,unix系統實現在內核中設有緩沖區高速緩存或頁高速緩存,當我們向文件寫入數據時,內核通常先將數據復制到緩沖區中,然后排入隊列,晚些時候再寫入磁盤),這種方式稱為延遲寫,等到緩沖區的空間被填滿,或者超過了指定的時限,或者內核需要重用緩沖區存放其它磁盤塊數據時,才會真正將緩沖區中的所有數據寫入到磁盤里面。
這種做法雖然提高了效率,但也為寫入數據帶來了安全問題,如果計算機停機,則保存在緩沖區中的寫入數據將丟失。為了保持一致性,即向文件寫入數據立即真正的寫入到磁盤上的文件中,而不是先寫到內存緩沖區里面,則我們需要采取文件同步。
簡單理解就是:
文件寫入:只是寫入到了內存緩沖區,可能還沒有寫到文件所擁有的磁盤數據塊上
文件同步:將緩沖區中的內容沖洗到磁盤上

  1. AOF觸發時機

當AOF開啟時,服務器在執行一個寫命令之后,同樣會將這個命令追加到aof_buf緩沖區,Redis事件循環根據配置項appendfsync策略確定調用flushAppendOnlyFile函數的時機進行數據落盤。其中appendfsync參數有三個可選配置項。

  • always Redis 在每個事件循環都要將 AOF 緩沖區中的所有內容寫入到 AOF 文件,並且同步 AOF 文件,所以 always 的效率是 appendfsync 選項三個值當中最差的一個,但從安全性來說,也是最安全的。當發生故障停機時,AOF 持久化也只會丟失一個事件循環中所產生的命令數據。
  • everysec Redis 在每個事件循環都要將 AOF 緩沖區中的所有內容寫入到 AOF 文件中,並且每隔一秒就要在子線程中對 AOF 文件進行一次同步。從效率上看,該模式足夠快。當發生故障停機時,只會丟失一秒鍾的命令數據。
  • no Redis 在每一個事件循環都要將 AOF 緩沖區中的所有內容寫入到 AOF 文件。而 AOF 文件的同步由操作系統控制。這種模式下速度最快,但是同步的時間間隔較長,出現故障時可能會丟失較多數據。

appendfsync的默認配置項為everysec,在考慮性能和數據安全性的同時,采取了一個折中的策略,即使數據丟失也最多損失一秒鍾的數據內容。

此外針對這三種刷盤策略,需要注意一個參數配置no-appendfsync-on-rewrite,這個里默認的配置為no,官方解釋信息為:

When the AOF fsync policy is set to always or everysec, and a background saving process (a background save or AOF log background rewriting) is performing a lot of I/O against the disk, in some Linux configurations Redis may block too long on the fsync() call. Note that there is no fix for this currently, as even performing fsync in a different thread will block our synchronous write(2) call.
In order to mitigate this problem it's possible to use the following option that will prevent fsync() from being called in the main process while a BGSAVE or BGREWRITEAOF is in progress.
This means that while another child is saving, the durability of Redis is the same as "appendfsync none". In practical terms, this means that it is possible to lose up to 30 seconds of log in the worst scenario (with the default Linux settings).
If you have latency problems turn this to "yes". Otherwise leave it as "no" that is the safest pick from the point of view of durability.

分析:當AOF的fsync策略設置為always或者everysec時,后台進程會產生大量的磁盤I/O,在一些Linux配置中Redis可能會因為fsync調用阻塞時間過長,目前沒有解決此問題的方法,因為即使在不同的線程中執行 fsync 也會阻塞同步 write(2) 調用。為了緩解這個問題,可以使用以下選項來防止在 BGSAVE 或 BGREWRITEAOF 正在進行時在主進程中調用 fsync()。當子進程正在保存時,Redis 的持久性與“appendfsync none”相同,在最壞的情況下(使用默認的 Linux 設置)可能會丟失多達 30 秒的日志。
結論:如果您有延遲問題,請將其設置為“yes”。 否則,將其保留為“no”,從耐用性的角度來看,這是最安全的選擇。

  1. AOF的還原

Redis如果同時開啟了RDB和AOF會優先使用AOF來進行數據恢復,在啟動過程中可以在啟動日志看到Redis對AOF文件的加載。

11118:M 21 Mar 2022 18:20:27.745 * DB loaded from append only file: 0.000 seconds
11118:M 21 Mar 2022 18:20:27.745 * Ready to accept connections

AOF文件中包含了數據庫重建所包含的所有寫命令,在啟動過程中只需要執行這些命令就可以恢復數據,啟動過程中會創建一個不帶網絡鏈接的偽客戶端(fake client)來讀取並執行AOF文件中的命令,直到所有命令被處理完畢,所有的數據就被恢復到Redis內存中。

  1. AOF配置項
  • aof-load-truncated yes/no

當AOF文件被截斷時,即AOF文件的最后命令不完整,如果此時啟動Redis,會將AOF數據加載回內存,此時便會出現問題。

  • yes:加載一個截斷的AOF,Redis服務器開始發出日志,通知用戶該事件;
  • no:服務器將中止並出現錯誤,拒絕啟動。

當我們得知AOF文件報錯時,可以用redis-check-aof 來修復出錯的 AOF 文件。

  • aof-use-rdb-preamble yes/no

在重寫AOF文件時,Redis能夠在AOF文件中使用RDB前導,以加快重寫和恢復速度。啟用此選項后,重寫的AOF文件由兩個不同的節組成RDB file、AOF tail。加載Redis時,會識別AOF文件以Redis字符串開頭,並加載帶前綴的RDB文件,然后繼續加載AOF尾部。

AOF重寫

AOF重寫功能是對AOF持久化功能的一個優化方案。AOF記錄了所有寫命令,持續的記錄會導致文件體積越來越大,而且還會出現一些無效命令(所謂無效命令指的是一個key被重復設置,我們不關心中間過程,只關心這個key的最終結果),因此Redis提供了重寫機制來壓縮文件體積,重寫后的AOF由於文件體積減小,也便於Redis的快速重啟。

  1. AOF重寫原理

Redis提供的AOF重寫功能,新文件在替換舊文件時並沒有通過讀舊文件中的寫命令來獲取結果,而是通過讀取服務器中最新的數據庫狀態來獲取值,這樣就需要關心執行過程。例如下面的命令中,如果記錄完整的寫命令需要記錄三條,但是服務器如果想用最少的命令來記錄當前結果,最簡單的辦法就是直接讀取數據庫來獲取當前值。

127.0.0.1:6379> set key hello
OK
127.0.0.1:6379> set key world
OK
127.0.0.1:6379> set key redis
OK
127.0.0.1:6379> get key
"redis"

AOF重寫的過程由aof_rewrite函數執行操作,在數據量較大時,這個過程會進行大量的寫操作並且會導致服務器阻塞。借鑒於RDB的實現方式,AOF的重寫由子進程來完成,父進程繼續處理客戶端請求,但是這樣同樣會出現RDB中數據不完整的問題,在子進程操作的過程中,父進程又處理了新的寫操作,就會導致數據不一致。為了解決這個問題,Redis服務器內置了一個AOF重寫緩沖區,這個緩沖區在子進程創建完成后開始使用,父進程會同時將寫命令發送至AOF緩沖區和AOF重寫緩沖區,執行過程如圖所示:
fork (1).svg

  • 當客戶端發起BGREWRITEAOF或者服務端通過配置自動觸發,此時檢測BGREWRITEAOF或者BGSAVE是否在執行,如果存在會延遲執行BGREWRITEAOF,在BGREWRITEAOF執行過程中,再發起BGSAVE會被拒絕。
  • 父進程會fork一個子進程來重寫AOF文件,當子進程完成重寫后會發送信號到父進程,父進程調用信號處理函數繼續對文件處理。
  • 父進程將AOF重寫緩沖區的所有內容寫入到新的AOF文件中,對新的AOF文件重命名原子的覆蓋現有AOF文件,完成新舊文件替換。
  • 信號處理函數執行完畢后,一次AOF備份完成,父進程正常處理請求,信號處理函數執行這個過程會造成父進程阻塞,其他時間不阻塞,盡可能低的降低對服務器的延時影響。
  1. AOF觸發條件

AOF 重寫可以由用戶通過調用 BGREWRITEAOF 手動觸發。或者通過配置項觸發,服務器在 AOF 功能開啟的情況下, 會維持以下三個變量:

  • 記錄當前 AOF 文件大小的變量 aof_current_size 。
  • 記錄最后一次 AOF 重寫之后, AOF 文件大小的變量 aof_rewrite_base_size 。
  • 增長百分比變量 aof_rewrite_perc 。

每次當 serverCron 函數執行時, 它都會檢查以下條件是否全部滿足, 如果是的話, 就會觸發自動的 AOF 重寫。redis.conf中提供了aof_current_size和aof_rewrite_perc相關的配置項:

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
  • auto-aof-rewrite-percentage:觸發AOF重寫的文件增長百分比,默認為100%,也就是當某次寫入后AOF膨脹為2倍會觸發AOF重寫。
  • auto-aof-rewrite-min-size:觸發AOF重寫的最小文件大小,默認為64M。

總結

Redis中提供了RDB和AOF兩種持久化方案,各自實現原理和適用場景都不同,下面對兩者的特點進行總結:

  1. RDB
  • 優勢
    • RDB是一個非常緊湊的單文件時間點的Redis數據表示,因此非常適合備份,可以傳輸到遙遠的數據中心,進行災難恢復。
    • RDB最大限度地提高了Redis的性能,因為Redis父進程需要做的唯一的工作是為了持久化Redis子進程,它將做所有其余的工作。父進程永遠不會執行磁盤I/O或類似的操作。
    • 此外與AOF相比,RDB允許使用大數據集更快地重新啟動。
  • 缺點
    • 由於RDB存儲的是時間間隔內某個時刻的數據快照,因此有可能會丟失一個時間間隔的據。
    • RDB經常需要fork(),以便使用子進程在磁盤上持久化。如果數據集很大,fork()可能會花費時間,如果數據集很大,CPU性能不是很好,可能會導致Redis停止為客戶端服務幾毫秒甚至一秒鍾。即使fork()使用了COW技術來優化內存占用,但此時如果出現大量key修改依然會需要大量的內存空間消耗。
  1. AOF
  • 優勢
    • AOF讓Redis的數據可靠性更高,可以使用不同的fsync策略always,everysec,no。在默認的每秒fsync策略下,寫性能仍然很好(fsync是使用后台線程執行的,當沒有fsync進行時,主線程會努力執行寫操作),在everysec策略下最多損失1秒的寫操作。
    • AOF日志是一個僅追加的日志,因此不存在查找,也不存在斷電時的損壞問題。即使日志由於某種原因(磁盤滿了或其他原因)導致AOF文件不完整,redis-check-aof工具也能夠輕松地修復它。
    • AOF以易於理解和解析的格式一個接一個地包含所有操作的日志。您甚至可以輕松地導出AOF文件。例如,即使你不小心用FLUSHALL命令刷新了所有的數據,只要在此期間沒有重寫日志,你仍然可以通過停止服務器,刪除最新的命令,重新啟動Redis來保存你的數據集。
  • 缺點
    • 對於同一個數據集,AOF文件通常比等效的RDB文件大。
    • AOF可能比RDB慢,這取決於具體的fsync策略。通常,fsync設置為每秒的性能仍然非常高,禁用fsync時,即使在高負載下,它也應該和RDB一樣快。即使在巨大的寫負載情況下,RDB的延遲明顯要比AOF要低。
    • 如果在重寫期間有寫入數據庫的操作(這些操作被緩沖在內存中,並在最后寫入新的AOF), AOF會使用大量內存。所有在重寫期間到達的寫命令都被寫入磁盤兩次。
  1. 結論

一般的建議是,如果你想要達到PostgreSQL所能提供的數據安全程度,你應該使用這兩種持久性方法。如果您非常關心您的數據,但是在發生災難時仍然可以忍受幾分鍾的數據丟失,那么您可以只使用RDB。有很多用戶單獨使用AOF,但是我們不鼓勵這樣做,因為對於進行數據庫備份、更快地重新啟動以及在AOF引擎出現bug的情況下,不時地使用RDB快照是一個很好的主意。

官方通知:

注意:由於所有這些原因,我們可能會在將來(長期計划)將AOF和RDB統一為一個持久性模型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM