Redis的持久化


為了防止數據丟失以及服務重啟時能夠恢復數據,Redis支持數據的持久化,主要分為兩種方式,分別是RDB和AOF.

RDB

RDB持久化是把當前進程數據生成快照保存到磁盤上的過程,由於是某一時刻的快照,那么快照中的值要早於或者等於內存中的值。
生成的rdb文件的名稱以及存儲位置由redis.conf中的dbfilenamedir兩個參數控制,默認生成的rdb文件是dump.rdb。

觸發方式

觸發rdb持久化的方式有2種,分別是手動觸發和自動觸發。

手動觸發

redis客戶端執行save命令和bgsave命令都可以觸發rdb持久化,但是兩者還是有區別的。

1.使用save命令時是使用redis的主進程進行持久化,此時會阻塞redis服務,造成服務不可用直到持久化完成,線上環境不建議使用;

2.bgsave命令是fork一個子進程,使用子進程去進行持久化,主進程只有在fork子進程時會短暫阻塞,fork操作完成后就不再阻塞,主進程可以正常進行其他操作。

3.bgsave是針對save阻塞主進程所做的優化,后續所有的自動觸發都是使用bgsave進行操作。

自動觸發

在以下4種情況時會自動觸發

  • redis.conf中配置save m n,即在m秒內有n次修改時,自動觸發bgsave生成rdb文件;

  • 主從復制時,從節點要從主節點進行全量復制時也會觸發bgsave操作,生成當時的快照發送到從節點;

  • 執行debug reload命令重新加載redis時也會觸發bgsave操作;

  • 默認情況下執行shutdown命令時,如果沒有開啟aof持久化,那么也會觸發bgsave操作;

關閉rdb持久化

如果要關閉rdb持久化可以用兩種方法:

  • 執行以下命令(redis-cli):
config set save ""
  • 修改配置文件
// 打開該行注釋
save ""

// 注釋掉以下內容
# save 900 1
# save 300 10
# save 60 10000

流程

rdb持久化的流程圖如下所示:

具體流程如下:

  1. redis客戶端執行bgsave命令或者自動觸發bgsave命令;
  2. 主進程判斷當前是否已經存在正在執行的子進程,如果存在,那么主進程直接返回;
  3. 如果不存在正在執行的子進程,那么就fork一個新的子進程進行持久化數據,fork過程是阻塞的,fork操作完成后主進程即可執行其他操作;
  4. 子進程先將數據寫入到臨時的rdb文件中,待快照數據寫入完成后再原子替換舊的rdb文件;
  5. 同時發送信號給主進程,通知主進程rdb持久化完成,主進程更新相關的統計信息(info Persitence下的rdb_*相關選項)。

優缺點

優點

  • RDB文件是某個時間節點的快照,默認使用LZF算法進行壓縮,壓縮后的文件體積遠遠小於內存大小,適用於備份、全量復制等場景;
  • Redis加載RDB文件恢復數據要遠遠快於AOF方式;

缺點

  • RDB方式實時性不夠,無法做到秒級的持久化;
  • 每次調用bgsave都需要fork子進程,fork子進程屬於重量級操作,頻繁執行成本較高;
  • RDB文件是二進制的,沒有可讀性,AOF文件在了解其結構的情況下可以手動修改或者補全;
  • 版本兼容RDB文件問題;

AOF

aof方式持久化是使用文本協議將每次的寫命令記錄到aof文件中,經過文件重寫后記錄最終的數據生成命令,在redis啟動時,通過執行aof文件中的命令恢復數據。

aof方式主要解決了數據實時性持久化的問題,aof方式對於兼顧數據安全性和性能非常有幫助。

開啟aof

開啟aof模式持久化需要修改redis.conf文件中的如下配置:

# 開啟aof
appendonly true

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

# aof文件存儲位置
dir ./

也可以在redis客戶端使用命令行的方式開啟或者關閉aof

# 開啟aof
config set appendonly yes

# 關閉aof
config set appendonly no

aof持久化流程

  • append
    aof文件只記錄寫命令,不記錄讀命令,當服務端接收到寫命令后,redis會將命令寫入到aof緩沖區中,之所以寫入緩沖區而不直接寫入aof文件中是因為如果每次都將命令直接寫入到文件中,那么redis的性能將完全取決於硬盤的讀寫能力,這與redis性能至上的理念不符,另外,寫入緩沖區中也便於使用不同的同步策略。

  • sync
    文件同步,即將aof緩沖區中的命令同步到aof文件中,redis提供三種策略以供選擇,由參數appendfsync控制,三種策略分別是:

always: 表示命令append到緩沖區以后調用系統fsync操作同步到aof文件中,fsync操作完成后主線程返回;

no: 表示命令寫入aof緩沖區后調用操作系統write操作,不對aof文件做fsync同步,同步到硬盤操作由操作系統負責,通常同步周期最長30秒;

everysec: 表示命令寫入aof緩沖區后調用操作系統write操作,write操作完成后主線程返回,由專門的線程每秒去進行fsync同步文件操作

默認使用everysec,兼顧性能和安全性,很顯然,使用always時每次都要等同步完成后才能返回,這個性能是很低的;同理使用no時,雖然不用每次都同步aof文件,但是同步操作周期不可控,數據安全性得不到保障,因此還是使用默認的everysec兼顧安全性和性能,每一秒同步一次,也就是在突發狀況下最多丟失1秒的數據。

  • 重寫(rewrite)

隨着寫命令越來越多,aof文件的體積也越來越大,此時就需要重寫機制來按照特定的機制清除或者合並命令從而達到減小文件體積,便於redis重啟加載的目的。

重寫機制

重寫規則

  • 進程內已經過期的數據不再寫入文件;
  • 只保存最終數據的寫入命令,如set a 1, set a 2, set a 3,此時只保留最終的set a 3;
  • 多條寫命令合並為一條命令,如lpush list 1, lpush list 2, lpush list 3合並為lpush list 1,2,3,同時為了防止單條命令過大,對於list、set、zset、hash等以64個元素為界限拆分為多條命令;

觸發

  • 手動觸發
    手動執行bgrewriteaof命令即可觸發aof重寫
  • 自動觸發
    自動觸發與redis.conf中的auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage配置有關,默認配置如下:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

auto-aof-rewrite-min-size: 表示觸發aof重寫時aof文件的最小體積,默認64m
auto-aof-rewrite-percentage: 表示當前aof文件空間和上一次重寫后aof文件空間的比值,默認是aof文件體積翻倍時觸發重寫

auto-aof-rewrite-percentage的計算方法:

auto-aof-rewrite-percentage =(當前aof文件體積 - 上次重寫后aof文件體積)/ 上次重寫后aof文件體積 * 100%

自動觸發的條件:
(當前aof文件體積 > auto-aof-rewrite-min-size) && (auto-aof-rewrite-percentage的計算值 > 配置文件中配置的auto-aof-rewrite-percentage值)

重寫流程

  1. 手動或者自動觸發文件重寫后主進程需要先判斷當前是否有子進程存在,如果存在則直接返回,不存在則fork子進程;
  2. fork操作完成后,主進程即可響應其他命令,在子進程生成新的aof文件過程中,主進程仍然維持原來的流程以保證原有aof機制的正確性;
  3. 在子進程生成新的aof文件過程中主進程執行的新命令同時會被寫入到aof重寫緩沖區中,當新aof文件生成后再將這一部分命令寫入到新aof文件中,防止數據丟失;
  4. 子進程根據內存快照,根據重寫規則生成新的aof文件,每次批量寫入硬盤數據量由配置aof-rewrite-incremental-fsync控制,默認為32MB,防止單次刷盤數據過多造成硬盤阻塞;
  5. 父進程把aof重寫緩沖區的數據寫入到新的aof文件中;
  6. 使用新aof文件替換舊的aof文件並發送信號給主進程表示重寫完成。

優缺點

優點

  • 數據安全性較高,每隔1秒同步一次數據到aof文件,最多丟失1秒數據;
  • aof文件相比rdb文件可讀性較高,便於災難恢復;

缺點

  • 雖然經過文件重寫,但是aof文件的體積仍然比rdb文件體積大了很多,不便於傳輸且數據恢復速度也較慢
  • aof的恢復速度要比rdb的恢復速度慢

fork以及copy_on_write

fork

不論RDB方式去創建一個新的rdb文件還是AOF方式重寫aof文件,都需要fork一個子進程去處理以便在不阻塞主進程的情況下完成rdb文件的生成以及aof文件的重寫,下面我們簡單了解一下什么是fork以及使用到的copy_on_write寫時復制技術。

何為fork?簡而言之就是創建一個主進程的副本,創建的子進程除了進程id,其余任何內容都和主進程完全一致,這就是fork。

fork創建的子進程獨立於主進程而存在,雖然兩個進程內存空間的內容完全一致,但是對於內存的寫入、修改以及文件的映射都是獨立的,兩個進程不會相互影響。

通過fork技術完美的解決了快照的問題,只需要某個時間點的內存中的數據,而父進程可以繼續對自己的內存進行修改、寫入而不會影響子進程的內存,這既不會阻塞主進程也不影響生成快照。

通過fork子進程的方式雖然能夠完美解決不阻塞的情況下創建快照的問題,但是又會引入以下的問題:

子進程和主進程擁有相同的內存空間,就相當於瞬間將內存的使用量提高了一倍,假設服務器是16GB內存,主進程占用10GB,那么此時再創建子進程還需奧10GB,很明顯超過了總內存,這很顯然是存在很大問題的,即使不超過總內存,fork時將內存使用量提高一倍也是不可取的。

COW

寫時拷貝(COW)就是為了解決這個問題而出現,那么什么是COW呢?

COW的主要作用就是將拷貝推遲到寫操作真正發生時,這也就避免了大量無意義的拷貝。

什么意思呢?

意思是說在fork子進程時,父子進程會被內核分配到不同的虛擬內存空間中,對於父子進程來說它們訪問的是不同的內存空間,但是兩個虛擬內存空間映射的仍然是相同的物理內存,也就是說在fork完成后未發生任何修改時,父子進程對應的物理內存是同一份。

如果此時主進程執行了修改或者寫入操作?因為有了修改或寫入操作,此時父子進程內存就會出現不一致的情況,由於是主進程進行的修改,因此內核會為主進程要修改的內存塊創建一個副本供主進程進行修改而不改變子進程的內存,也就是誰發生了修改就要為誰創建相應的副本。

linux中內存的復制是以內存頁為單位的(4KB),也就是會為發生改變的內存頁創建副本。

COW技術彌補了fork進程時內存翻倍的情況,fork操作為子進程訪問父進程提供了支持,COW減少了額外的開銷,這兩者是Redis能夠使用子進程進行快照持久化的核心。

COW原理:

fork()之后,kernel把父進程中所有的內存頁的權限都設為read-only,然后子進程的地址空間指向父進程。當父子進程都只讀內存時,相安無事。當其中某個進程寫內存時,CPU硬件檢測到內存頁是read-only的,於是觸發頁異常中斷(page-fault),陷入kernel的一個中斷例程。中斷例程中,kernel就會 把觸發的異常的頁復制一份,於是父子進程各自持有獨立的一份。

COW優點:

  • 減少不必要的資源分配,只有在發生改變時才創建修改的內存頁的副本,而不是創建整個內存的副本;
  • 減少fork子進程的時間,因為cow的存在,fork子進程時只需要復制主進程的空間內存頁表即可,而不需要復制物理內存,因此大大提高了fork子進程的速度。

COW缺點:

  • 如果fork之后,父子進程都需要進行大量修改,那么就會出現大量的分頁錯誤(頁異常中斷page-fault),這就有點得不償失了。

但是對於redis來說,子進程只是用來生成快照的,並不會進行修改或者寫入操作,也就不存在上述所說的問題了。

重啟加載

Redis支持單獨啟動RDB或者單獨啟用AOF,也支持同時啟用RDB和AOF,redis重啟時加載流程如下所示:

  1. redis重啟時判斷是否開啟aof,如果開啟了aof,那么就優先加載aof文件;
  2. 如果aof存在,那么就去加載aof文件,加載成功的話redis重啟成功,如果aof文件加載失敗,那么會打印日志表示啟動失敗,此時可以去修復aof文件后重新啟動;
  3. 若aof文件不存在,那么redis就會轉而去加載rdb文件,如果rdb文件不存在,redis直接啟動成功;
  4. 如果rdb文件存在就會去加載rdb文件恢復數據,如加載失敗則打印日志提示啟動失敗,如加載成功,那么redis重啟成功,且使用rdb文件恢復數據;

持久化過程中需要注意的問題

aof追加阻塞

aof追加阻塞是指在開啟aof持久化時,默認使用的是everysec同步策略,此時有一個額外的線程同步aof緩沖區中的內容到磁盤上的aof文件,如果在同步過程中由於磁盤io過高導致的redis主進程阻塞;

出現aof阻塞的根本原因是磁盤負載過高,redis主進程會監控同步線程每次同步aof緩沖區內容到aof文件所耗費的時間,如果距離上次同步成功的時間在2s內,那么主線程就直接返回,如果距離上次同步成功的時間超過2s,redis主進程就會阻塞,直到同步完成。

具體的流程圖如下所示:

發生aof追加阻塞時會嚴重影響redis的性能,造成該現象的主要原因是磁盤高負載,那么相應的解決方案也要從磁盤負載上來解決。

解決方案:

  1. redis盡量不要與其他高磁盤消耗的服務部署在一起,如rabbitmq等消息隊列,mysql等數據庫服務;
  2. 配置開啟no-appendfsync-on-rewrite=yes,表示在重寫期間不做fsync操作;
  3. 單機配置多個redis實例的情況下,不同實例分盤存儲aof文件以減輕單個磁盤的壓力;

fork阻塞耗時問題

無論生成rdb文件還是重寫aof文件,都會使用fork創建一個子進程來處理,這樣就不會阻塞主進程了,雖然fork出來的子進程不會阻塞主進程,但是fork的過程中還是會阻塞主進程。也就是說子進程創建過程中還是會阻塞主進程影響redis對外提供服務。

前面提過fork過程中使用寫時復制技術,並不會真正的復制物理內存,但是會復制主進程的空間內存頁表,例如主進程為10G內存,大概要復制20M的空間頁表,也就是說主進程內存越大,需要復制的空間內存頁表越大,fork所需的時間越長,redis阻塞的時間越長,因此fork操作的優化點在於主進程的內存大小,另外有的虛擬化技術也會加大fork的時間,如Xen虛擬機。

因此從主進程內存和虛擬機化技術兩個方面來優化fork阻塞耗時問題:

  1. 盡量使用物理機或者高效支持fork的虛擬化技術;
  2. 控制Redis實例最大可用內存,fork耗時和redis主進程內存量成正比;
  3. 降低fork操作的頻率,如調高auto-aof-rewrite-min-size的值以減少aof重寫的次數,或者主從復制時減少全量復制等;
  4. 合理配置linux內存分配策略,防止由於物理內存不足導致的fork失敗;


免責聲明!

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



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