Redis 中如何保證資料的不丟失,Redis 中的持久化是如何進行的

Rick.lz發表於2022-02-09

Redis 中資料的持久化

前言

我們知道 Redis 是記憶體資料庫,所有操作都在記憶體上完成。記憶體的話,伺服器斷電,記憶體上面的資料就會丟失了。這個問題顯然是需要解決的。

Redis 中引入了持久化來避免資料的丟失,主要有兩種持久化的方式 RDB 持久化和 AOF 持久化。

AOF 持久化

什麼是 AOF 持久化

AOF(Append Only File):通過儲存資料庫執行的命令來記錄資料庫的狀態。

redis

AOF日誌對資料庫命令的儲存順序是,Redis 先執行命令,把資料寫入記憶體,然後才記錄日誌。

為什麼要後記錄日誌呢

1、後寫,能夠避免記錄到錯誤的命令。因為是先執行命令,後寫入日誌,只有命令執行成功了,命令才能被寫入到日誌中。

2、避免阻塞當前的寫操作,是在命令執行後才記錄日誌,所以不會阻塞當前的寫操作。

AOF 的潛在風險

  • 1、如果命令執行成功,寫入日誌的時候當機了,命令沒有寫入到日誌中,這時候就有丟失資料的風險了,因為這時候沒有寫入日誌,服務斷電之後,這部分資料就丟失了。

這種場景在別的地方也很常見,比如基於 MQ 實現分散式事務,也會出現業務處理成功 + 事務訊息傳送失敗這種場景,RabbitMQ,RocketMQ,Kafka 事務性,訊息丟失和訊息重複傳送的處理策略

  • 2、AOF 的日誌寫入也是在主執行緒進行的,如果磁碟的壓力很大,寫入速度變慢了,會影響後續的操作。

這兩種情況可通過調整 AOF 檔案的寫入磁碟的時機來避免

AOF 檔案的寫入和同步

AOF 檔案持久化的功能分成三個步驟,檔案追加(append),檔案寫入,檔案同步(sync)。

AOF 檔案在寫入磁碟之前是先寫入到 aof_buf 緩衝區中,然後通過呼叫 flushAppendOnlyFile 將緩衝區中的內容儲存到 AOF 檔案中。

寫入的策略通過 appendfsync 來進行配置

  • Always:同步寫回 每次寫操作命令執行完後,同步將 AOF 日誌資料寫回硬碟;

  • Everysec:每秒寫回 每次寫操作命令執行完後,先將命令寫入到 AOF 檔案的核心緩衝區,然後每隔一秒將緩衝區裡的內容寫回到硬碟;

  • No:作業系統控制的寫回 Redis 不在控制命令的寫會時機,交由系統控制。每次寫操作命令執行完成之後,命令會被放入到 AOF 檔案的核心緩衝區,之後什麼時候寫入到磁碟,交由系統控制。

AOF 檔案重寫機制

因為每次執行的命令都會被寫入到 AOF 檔案中,隨著系統的執行,越來越多的檔案會被寫入到 AOF 檔案中,這樣 AOF 檔案勢必會變得很大,這種情況該如何去處理呢?

為了解決這種情況,Redis 中引入了重寫的機制

什麼是重寫呢?

因為 AOF 檔案中記錄的是每個命令的操作記錄,舉個?,比如當一個鍵值對被多條寫命令反覆修改時,AOF檔案會記錄相應的多條命令,那麼重寫機制,就是根據這個鍵值對當前的最新狀態,為它生成對應的寫入命令,儲存成一行操作命令。這樣就精簡了 AOF 檔案的大小。

192.168.56.118:6379> set name "xiaoming"
OK
192.168.56.118:6379> get name
"xiaoming"
192.168.56.118:6379> set name "xiaozhang"
OK
192.168.56.118:6379> set name "xiaoli"
OK

# 重寫後就是
192.168.56.118:6379> set name "xiaoli"

簡單來講就是多變一,就是把 AOF 中日誌根據當前鍵值的狀態,合併成一條操作命令。

重寫之後的檔案會儲存到新的 AOF 檔案中,這時候舊的 AOF 檔案和新的 AOF 檔案中鍵值對的狀態是一樣的。然後新的 AOF 檔案會替換掉舊的 AOF 檔案,這樣 重寫操作一直在進行,AOF 檔案就不至於變的過大。

重寫是後臺進行的, AOF 的重寫會放到子程式中進行的,使用子程式的優點:

1、子程式處理 AOF 期間,不會影響 Redis 主執行緒對資料的處理;

2、子程式擁有所線上程的資料副本,使用程式能夠避免鎖的使用,保證資料的安全。

這裡來看下,AOF 的處理流程

AOF 重寫也有一個緩衝區,當服務節接收到新的命令的是,如果在正在進行 AOF 重寫,命令同樣也會被髮送到 AOF 緩衝區

redis

子程式執行 AOF 重寫的過程,服務端程式主要處理以下內容

1、接收並處理客戶端傳送的命令;

2、將執行後的命令寫入到 AOF 緩衝區;

3、將執行後的命令也寫入到 AOF 重寫緩衝區;

AOF 緩衝區和 AOF 重寫緩衝區中的內容會被定期的同步到 AOF 檔案和 AOF 重寫檔案中

當子程式完成重寫的時候,會給父程式傳送一個訊號,這時候父程式主要主要進行下面的兩步操作:

1、將 AOF 重寫緩衝區中的內容全部寫入到 AOF 重寫檔案中,這時候重寫 AOF 檔案儲存的資料狀態是和服務端資料庫的狀態一致的;

2、將 AOF 重寫檔案替換舊的 AOF 檔案;

通過 AOF 的重寫操作,新的 AOF 檔案不斷的替換舊的 AOF 檔案,這樣就能控制 AOF 檔案的大小

AOF 的資料還原

AOF 檔案包了重建資料庫索引鎖需要的全部命令,所以只需要讀入並重新執行一遍 AOF 檔案中儲存的命令,即可還原服務關閉之前資料庫的狀態。

RDB 持久化

什麼是 RDB 持久化

RDB(Redis database):實現方式是將存在 Redis 記憶體中的資料寫入到 RDB 檔案中儲存到磁碟上從而實現持久化的。

和 AOF 不同的是 RDB 儲存的是資料而不是操作,在進行資料恢復的時候,直接把 RDB 的檔案讀入到記憶體,即可完成資料恢復。

redis

RDB 如何做記憶體快照

Redis 中對於如何備份資料到 RDB 檔案中,提供了兩種方式

  • 1、save: 在主執行緒中執行,不過這種會阻塞 Redis 服務程式;

  • 2、bgsave: 主執行緒會 fork 出一個子程式來負責處理 RDB 檔案的建立,不會阻塞主執行緒的命令操作,這也是 Redis 中 RDB 檔案生成的預設配置;

對於 save 和 bgsave 這兩種快照方式,服務端是禁止這兩種方式同時執行的,防止產生競爭條件。

Redis 中可以使用 save 選項,來配置服務端執行 BGSAVE 命令的間隔時間

#
# 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
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1
save 300 10
save 60 10000

save 900 1 就是服務端在900秒,讀資料進行了至少1次修改,就會觸發一次 BGSAVE 命令

save 300 10 就是服務端在300秒,讀資料進行了至少10次修改,就會觸發一次 BGSAVE 命令

快照時發生資料修改

舉個例子?:我們在t時刻開始對記憶體資料內進行快照,假定目前有 2GB 的資料需要同步,磁碟寫入的速度是 0.1GB/s 那麼,快照的時間就是 20s,那就是在 t+20s 完成快照。

如果在 t+6s 的時候修改一個還沒有寫入磁碟的記憶體資料 test 為 test-hello。那麼就會破壞快照的完整性了,因為 t 時刻備份的資料已經被修改了。當然是希望在備份期間資料不能被修改。

如果不能被修改,就意味這在快照期間不能對資料進行修改操作,就如上面的栗子,快照需要進行20s,期間不允許處理資料更新操作,這顯然也是不合理的。

這裡需要聊一下 bgsave 是可以避免阻塞,不過需要注意的是避免阻塞和正常讀寫操作是有區別的。避免阻塞主執行緒確實沒有阻塞可以處理讀操作,但是為了保護快照的完整性,是不能修改快照期間的資料的。

這裡就需要引入一種新的處理方案,寫時複製技術(Copy-On-Write, COW),在執行快照的同時,正常處理寫操作。

bgsave 子程式是由主執行緒 fork 生成的,所以是可以共享主執行緒的記憶體的,bgsave子程式執行後會讀取主執行緒中的記憶體資料,並且寫入到 RDB 檔案中。

寫複製技術就是,如果主執行緒在記憶體快照期間修改了一塊記憶體,那麼這塊記憶體會被複制一份,生成該資料的副本,然後 bgsave 子程式在把這段記憶體寫入到 RDB 檔案中。這樣就可以在快照期間進行資料的修改了。

redis

多久做一次快照

對於快照,如果做的太頻繁,可能會出現前一次快照還沒有處理完成,後面的快照資料馬上就進來了,同時過於頻繁的快照也會增加磁碟的壓力。

如果間隔時間過久,伺服器在兩次快照期間當機,丟失的資料大小會隨著快照間隔時間的增長而增加。

是否可以選擇增量式快照呢?選擇增量式快照,我們就需要記住每個鍵值對的狀態,如果鍵值對很多,同樣也會引入很多記憶體空間,這對於記憶體資源寶貴的Redis來說,有些得不償失。

相較於 AOF 來對比,RDB 是會在資料恢復時,速度更快。但是 RDB 的記憶體快照同步頻率不太好控制,過多過少都有問題。

Redis 4.0中提出了一個混合使用 AOF 日誌和記憶體快照的方法。簡單來說,記憶體快照以一定的頻率執行,在兩次快照之間,使用AOF日誌記錄這期間的所有命令操作。

通過混合使用AOF日誌和記憶體快照的方法,RDB 快照的頻率不需要過於頻繁,在兩次 RDB 快照期間,使用 AOF 日誌來記錄,這樣也不用考慮 AOF 的檔案過大問題,在下一次 RDB 快照開始的時候就可以刪除 AOF 檔案了。

redis

過期的鍵如何持久化

在生成 RDB 檔案的過程中,如果一個鍵已經過期,那麼其不會被儲存到 RDB 檔案中。在載入 RDB 的時候,要分兩種情況:

  • 1、如果 Redis 以主伺服器的模式執行,那麼會對 RDB 中的鍵進行時間檢查,過期的鍵不會被恢復到 Redis 中。

  • 2、如果 Redis 以從伺服器的模式執行,那麼 RDB 中所有的鍵都會被載入,忽略時間檢查。在從伺服器與主伺服器進行資料同步的時候,從伺服器的資料會先被清空,所以載入過期鍵不會有問題。

對於 AOF 來說,如果一個鍵過期了,那麼不會立刻對 AOF 檔案造成影響。因為 Redis 使用的是惰性刪除和定期刪除,只有這個鍵被刪除了,才會往 AOF 檔案中追加一條 DEL 命令。在重寫 AOF 的過程中,程式會檢查資料庫中的鍵,已經過期的鍵不會被儲存到 AOF 檔案中。

在執行過程中,對於主從複製的 Redis,主伺服器和從伺服器對於過期鍵的處理也不相同:

  • 1、對於主伺服器,一個過期的鍵被刪除了後,會向從伺服器傳送 DEL 命令,通知從伺服器刪除對應的鍵;

  • 2、從伺服器接收到讀取一個鍵的命令時,即使這個鍵已經過期,也不會刪除,而是照常處理這個命令;

  • 3、從伺服器接收到主伺服器的 DEL 命令後,才會刪除對應的過期鍵。

這樣保證了資料的一致性,一個鍵值對存在於主伺服器,也必然存在於從伺服器。

總結

AOF

優點:AOF 中有三種策略可以進行選擇,AOF 的預設策略為每秒鐘 fsync 一次,在這種配置下,Redis 仍然可以保持良好的效能,並且就算髮生故障停機,也最多隻會丟失一秒鐘的資料。

缺點:AOF 檔案體積一般情況下比 RDB 檔案體積大,並且資料還原速度也慢於 RDB。

RDB

優點:可以快速恢復資料,相比於 AOF 的順序,逐一執行操作命令,效率更高;

缺點:因為是記憶體快照,頻率過快,過慢,都會有響應的問題。過快,浪費磁碟資源,會給磁碟造成壓力,過慢會存在較多資料丟失的問題。

Redis 4.0中提出了一個混合使用 AOF 日誌和記憶體快照的方法,如果想要保證資料不丟失,這是一個比較好的選擇;

如果允許分鐘級別的資料丟失,可以只使用RDB;

如果只用AOF,優先使用 everysec 的配置選項,因為它在可靠性和效能之間取了一個平衡。

參考

【Redis核心技術與實戰】https://time.geekbang.org/column/intro/100056701
【Redis設計與實現】https://book.douban.com/subject/25900156/
【過期鍵與持久化】https://segmentfault.com/a/1190000017526315
【Redis 中如何保證資料不丟失,持久化是如何進行的】https://boilingfrog.github.io/2022/01/07/redis中如何進行資料持久化/

相關文章