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


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中如何進行數據持久化/


免責聲明!

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



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