首頁 > 軟體

一文詳解Redis中的持久化

2022-09-20 22:03:48

1. 前言

為什麼要進行持久化?:持久化功能有效地避免因程序退出造成的資料丟失問題,當下次重啟時利用之前持久化的檔案即可實現資料恢復。

持久化都有那些方式?:Redis支援RDB和AOF兩種持久化機制。

2. RDB

RDB持久化是把當前程序資料生成快照儲存到硬碟的過程,觸發RDB持久化過程分為手動觸發自動觸發

2.1 手動觸發

手動觸發分別對應savebgsave命令:

  • save 命令::阻塞當前Redis伺服器,直到RDB過程完成為止,對於記憶體比較大的範例會造成長時間阻塞,線上環境不建議使用。
  • bgsave 命令:Redis程序執行fork操作建立子程序,RDB持久化過程由子程序負責,完成後自動結束。阻塞只發生在fork階段,一般時間很短。

顯然bgsave命令是針對save阻塞問題做的優化。因此Redis內部所有的涉及RDB的操作都採用bgsave的方式,而save命令已經廢棄。

2.2 自動觸發

  • 使用save相關設定,如save m n。表示m秒內資料集存在n次修改時,自動觸發bgsave。
  • 如果從節點執行全量複製操作,主節點自動執行bgsave生成RDB檔案並行送給從節點。
  • 執行debug reload命令重新載入Redis時,也會自動觸發save操作。
  • 預設情況下執行shutdown命令時,如果沒有開啟AOF持久化功能則自動執行bgsave。

3. bgsave大致流程

流程說明:

  • 執行bgsave命令,Redis父程序判斷當前是否存在正在執行的子程序,如RDB/AOF子程序,如果存在bgsave命令直接返回。
  • 父程序執行fork操作建立子程序,fork操作過程中父程序會阻塞,通過info stats命令檢視latest_fork_usec選項,可以獲取最近一個fork操作的耗時,單位為微秒。
  • 父程序fork完成後,bgsave命令返回“Background saving started”資訊並不再阻塞父程序,可以繼續響應其他命令。
  • 子程序建立RDB檔案,根據父程序記憶體生成臨時快照檔案,完成後對原有檔案進行原子替換。執行lastsave命令可以獲取最後一次生成RDB的時間,對應info統計的rdb_last_save_time選項。
  • 程序傳送訊號給父程序表示完成,父程序更新統計資訊,具體見info Persistence下的rdb_*相關選項。

關於RDB檔案:

  • 儲存位置:RDB檔案儲存在dir設定指定的目錄下,檔名通過dbfilename設定指定。可以通過執行config set dir{newDir}和config set dbfilename{newFileName}執行期動態執行,當下次執行時RDB檔案會儲存到新目錄。
  • 壓縮:Redis預設採用LZF演演算法對生成的RDB檔案做壓縮處理,壓縮後的檔案遠遠小於記憶體大小,預設開啟,可以通過引數config set rdbcompression{yes|no}動態修改。
  • 校驗:如果Redis載入損壞的RDB檔案時拒絕啟動,並列印 # Short read or OOM loading DB. Unrecoverable error, aborting now. (可以使用Redis提供的redis-check-dump工具檢測RDB檔案並獲取對應的錯誤報告)。

4. RDB持久化方式的優缺點

優點:

  • RDB是一個緊湊壓縮的二進位制檔案,代表Redis在某個時間點上的資料快照。非常適用於備份,全量複製等場景。比如每6小時執行bgsave備份,並把RDB檔案拷貝到遠端機器或者檔案系統中(如hdfs),用於災難恢復。(資料緊湊,便於儲存)
  • Redis載入RDB恢復資料遠遠快於AOF的方式。(恢復速度快)

缺點:

  • RDB方式資料沒辦法做到實時持久化/秒級持久化。因為bgsave每次執行都要執行fork操作建立子程序,屬於重量級操作,頻繁執行成本過高。(成本高)
  • RDB檔案使用特定二進位制格式儲存,Redis版本演進過程中有多個格式的RDB版本,存在老版本Redis服務無法相容新版RDB格式的問題。(不相容)

5. AOF

AOF(append only file)持久化:以獨立紀錄檔的方式記錄每次寫命令,重啟時再重新執行AOF檔案中的命令達到恢復資料的目的。主要作用是解決了資料持久化的實時性。

6. AOF的使用方式

  • 開啟AOF功能需要設定設定:appendonly yes,預設不開啟。
  • AOF檔名通過appendfilename設定設定,預設檔名是appendonly.aof。(路徑同RDB)
  • 主要流程有命令寫入(append)、檔案同步(sync)、檔案重寫(rewrite)、重啟載入(load)。

7. AOF流程剖析

流程描述:

  • 所有的寫入命令會追加到aof_buf(緩衝區)中。
  • AOF緩衝區根據對應的策略向硬碟做同步操作。
  • 隨著AOF檔案越來越大,需要定期對AOF檔案進行重寫,達到壓縮的目的。
  • 當Redis伺服器重啟時,可以載入AOF檔案進行資料恢復。

7.1 命令寫入

AOF命令寫入的內容直接是文字協定格式。例如set hello world這條命令,在AOF緩衝區會追加如下文字:

*3rn$3rnsetrn$5rnhellorn$5rnworldrn

為什麼使用文字協定格式?

  • 文字協定具有很好的相容性。(相容性好)
  • 開啟AOF後,所有寫入命令都包含追加操作,直接採用協定格式,避免了二次處理開銷。(處理簡單)
  • 文字協定具有可讀性,方便直接修改和處理。(方便修改)

為什麼要追加到aof_buf中而不是直接寫入硬碟?

  • 如果每次寫AOF檔案命令都直接追加到硬碟,那麼Redis的效能就會受到硬碟讀寫速度的影響,而硬碟的讀寫速度相對於記憶體則是數量級上的差距,所以如果每次直接寫入硬碟則勢必會大幅度影響Redis的執行速度。(影響執行速度)
  • 使用緩衝區暫存,Redis還可以提供多種緩衝區同步硬碟的策略,在效能和安全性方面做出平衡。(可以針對具體場景干預刷盤策略,以達到更好的效果)

7.2 檔案同步

Redis提供了多種AOF緩衝區同步檔案策略,由引數appendfsync控制。

系統呼叫write和fsync的幾點說明:

  • write :會觸發延遲寫(delayed write)機制。Linux在核心提供頁緩衝區用來提高硬碟IO效能。write操作在寫入系統緩衝區後直接返回。同步硬碟操作依賴於系統排程機制,例如:緩衝區頁空間寫滿或達到特定時間週期。同步檔案之前,如果此時系統故障宕機,緩衝區內資料將丟失。(寫緩衝,定期由作業系統刷盤)
  • fsync :針對單個檔案操作(比如AOF檔案),做強制硬碟同步,fsync將阻塞直到寫入硬碟完成後返回,保證了資料持久化。(立即將緩衝資料刷盤)

策略的幾點說明:

  • always:每次寫入都要同步AOF檔案,在一般的SATA硬碟上,Redis只能支援大約幾百TPS寫入,顯然跟Redis高效能特性背道而馳,不建議設定。
  • no:由於作業系統每次同步AOF檔案的週期不可控,而且會加大每次同步硬碟的資料量,雖然提升了效能,但資料安全性無法保證。
  • everysec:是建議的同步策略,也是預設設定,做到兼顧效能和資料安全性。(在系統突然宕機的情況下丟失1~2秒的資料)

7.3 重寫機制

為什麼要重寫?:

  • 隨著命令不斷寫入AOF,檔案會越來越大。
  • 會包含越來越多無用的命令記錄。(比如最近一次對一個值的更新操作,那麼在此之前記錄的更新操作都會作廢)
  • 更小的AOF檔案可以更快地被Redis載入。

怎麼重寫?:

  • AOF檔案重寫就是把Redis程序內的資料轉化為寫命令同步到新AOF檔案。

重寫後那些優化讓檔案變小了?:

  • 程序內已經超時的資料不再寫入檔案。(去除失效資料)
  • 舊的AOF檔案含有無效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重寫使用程序內資料直接生成,這樣新的AOF檔案只保留最終資料的寫入命令。(去除無用命令)
  • 多條寫命令可以合併為一個,如:lpush list a、lpush list b、lpush list c可以轉化為:lpush list a b c;為了防止單條命令過大造成客戶 端緩衝區溢位,對於list、set、hash、zset等型別操作,以64個元素為界拆分為多條。(使用批次命令)

重寫有那些觸發方式?:

  • 手動觸發 :直接呼叫bgrewriteaof命令。
  • 自動觸發 :根據auto-aof-rewrite-min-size和auto-aof-rewritepercentage引數確定自動觸發時機。
    • auto-aof-rewrite-min-size:表示執行AOF重寫時檔案最小體積,預設為64MB。(根據當前檔案大小)
    • auto-aof-rewrite-percentage:代表當前AOF檔案空間(aof_current_size)和上一次重寫後AOF檔案空間(aof_base_size)的比值。(根據檔案大小的增量)

自動觸發時機:

aof_current_size > auto-aof-rewrite-minsize && (aof_current_size-aof_base_size)/aof_base_size >= auto-aof-rewrite-percentage

aof_current_size 和 aof_base_size 可以在info Persistence統計資訊中檢視。

重寫流程概述:

流程描述:

  • 執行AOF重寫請求。

    • 如果當前程序正在執行AOF重寫,請求不執行並返回 ERR Background append only file rewriting already in progress 。
    • 如果當前程序正在執行bgsave操作,重寫命令延遲到bgsave完成之後再執行,返回 Background append only file rewriting scheduled
  • 父程序執行fork建立子程序,開銷等同於bgsave過程。
  • (1)主程序fork操作完成後,繼續響應其他命令。所有修改命令依然寫入AOF緩衝區並根據appendfsync策略同步到硬碟,保證原有AOF機制正確性。(2)由於fork操作運用寫時複製技術(Copy On Write),子程序只能共用fork操作時的記憶體資料。由於父程序依然響應命令,Redis使用“AOF重寫緩衝區”儲存這部分新資料,防止新AOF檔案生成期間丟失這部分資料。
  • 子程序根據記憶體快照,按照命令合併規則寫入到新的AOF檔案。每次批次寫入硬碟資料量由設定aof-rewrite-incremental-fsync控制,預設為32MB,防止單次刷盤資料過多造成硬碟阻塞。
  • (1)新AOF檔案寫入完成後,子程序傳送訊號給父程序,父程序更新統計資訊,具體見info persistence的aof_*相關統計。(2)父程序把AOF重寫緩衝區的資料寫入到新的AOF檔案(3)並使用新AOF檔案替換老檔案,完成AOF重寫。

7.4 重啟載入

流程描述:

  • AOF持久化開啟且存在AOF檔案時,優先載入AOF檔案,並輸出 DB loaded from append only file: xxx seconds 。
  • AOF關閉或者AOF檔案不存在時,載入RDB檔案,並輸出 DB loaded from disk: xxx seconds 。
  • 載入AOF或RDB檔案成功後,Redis啟動成功。
  • AOF或RDB檔案存在錯誤時,Redis啟動失敗並列印錯誤資訊。

關於檔案校驗:

載入損壞的AOF檔案時會拒絕啟動,並會輸出:

Bad file format reading the append only file: make a backup of your AOF file,then use ./redis-check-aof --fix <filename>

對於錯誤格式的AOF檔案:先進行備份,然後採用redis-check-aof --fix命令進行修復,修復後使用diff-u對比資料的差異,找出丟失的資料,有些可以人工修改補全。

對於AOF檔案結尾不完整:比如機器突然掉電導致AOF尾部檔案命令寫入不全。Redis為我們提供了aof-load-truncated設定來相容這種情況,預設開啟。載入AOF時,當遇到此問題時會忽略並繼續啟動,同時列印如下警告紀錄檔:

# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 397856725 !!!
# AOF loaded anyway because aof-load-truncated is enabled

8. 問題定位與優化

8.1 關於fork操作

當Redis做RDB或AOF重寫時,一個必不可少的操作就是執行fork操作建立子程序,對於大多數作業系統來說fork是個重量級操作雖然fork建立的子程序不需要拷貝父程序的實體記憶體空間,但是會複製父程序的空間記憶體頁表,因此fork操作耗時跟程序總記憶體量息息相關。可以在info stats統計中查latest_fork_usec指標獲取最近一次fork操作耗時,單位微秒。

減少fork耗時的措施:

  • 優先使用物理機或者高效支援fork操作的虛擬化技術,避免使用Xen。
  • 控制Redis範例最大可用記憶體,fork耗時跟記憶體量成正比,線上建議每個Redis範例記憶體控制在10GB以內。
  • 合理設定Linux記憶體分配策略,避免實體記憶體不足導致fork失敗。
  • 降低fork操作的頻率,如適度放寬AOF自動觸發時機,避免不必要的全量複製等。

8.2 關於子程序開銷

CPU:

  • 分析:子程序負責把程序內的資料分批寫入檔案,這個過程屬於CPU密集操作,通常子程序對單核CPU利用率接近90%。
  • 優化:
    • Redis是CPU密集型服務,不要做繫結單核CPU操作。由於子程序非常消耗CPU,會和父程序產生單核資源競爭。
    • 不要和其他CPU密集型服務部署在一起,造成CPU過度競爭。
    • 如果部署多個Redis範例,儘量保證同一時刻只有一個子程序執行重寫工作。

記憶體:

  • 分析:得益於Linux的寫時複製機制(copy on write),父子程序會共用相同的實體記憶體頁,當父程序處理寫請求時會把要修改的頁建立副本,而子程序在fork操作過程中共用整個父程序記憶體快照。(重寫時共用同一份實體記憶體區域,記憶體主要開銷在於 拷貝的頁表 和 應用 copy on write 時某些頁的拷貝 以及在進行AOF重寫所使用的 aof_rewrite_buf佔用的大小 )
  • 優化:
    • 如果部署多個Redis範例,儘量保證同一時刻只有一個子程序在工作。
    • 避免在大量寫入時做子程序重寫操作,這樣將導致父程序維護大量頁副本,造成記憶體消耗。
    • Linux kernel在2.6.38核心增加了Transparent Huge Pages(THP),支援huge page(2MB)的頁分配,預設開啟。當開啟時可以降低fork創 建子程序的速度,但執行fork之後,如果開啟THP,複製頁單位從原來4KB變為2MB,會大幅增加重寫期間父程序記憶體消耗。

硬碟:

  • 分析:子程序主要職責是把AOF或者RDB檔案寫入硬碟持久化,所以在執行重寫的時候勢必會增加硬碟的寫入壓力。根據Redis重寫AOF或RDB的資料量,結合系統工具如sar、iostat、iotop等,可分析出重寫期間硬碟負載情況。
  • 優化:
    • 不要和其他高硬碟負載的服務部署在一起。如:儲存服務、訊息佇列服務等。
    • AOF重寫時會消耗大量硬碟IO,可以開啟設定no-appendfsyncon-rewrite,預設關閉。表示在AOF重寫期間不做fsync操作,注意!設定no-appendfsync-on-rewrite=yes時,在極端情況下可能丟失整個AOF重寫期間的資料,需要根據資料安全性決定是否設定。
    • 當開啟AOF功能的Redis用於高流量寫入場景時,Redis的效能會受到硬碟寫入效能的影響。
    • 對於單機設定多個Redis範例的情況,可以設定不同範例分盤儲存AOF檔案,分攤硬碟寫入壓力。

8.3 關於AOF追加阻塞

描述:當開啟AOF持久化時,常用的同步硬碟的策略是everysec,用於平衡效能和資料安全性。對於這種方式,Redis使用另一條執行緒每秒執行fsync同步硬碟。當系統硬碟資源繁忙時,會造成Redis主執行緒阻塞。

問題定位:

  • 發生AOF阻塞時,Redis輸出紀錄檔,用於記錄AOF fsync阻塞導致拖慢Redis服務的行為: Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redi
  • 每當發生AOF追加阻塞事件發生時,在info Persistence統計中,aof_delayed_fsync指標會累加,檢視這個指標方便定位AOF阻塞問題。
  • AOF同步最多允許2秒的延遲,當延遲發生時說明硬碟存在高負載問題。

流程概述:

  • 主執行緒負責寫入AOF緩衝區。
  • AOF執行緒負責每秒執行一次同步磁碟操作,並記錄最近一次同步時間。
  • 主執行緒負責對比上次AOF同步時間:
    • 如果距上次同步成功時間在2秒內,主執行緒直接返回。
    • 如果距上次同步成功時間超過2秒,主執行緒將會阻塞,直到同步操作完成。

也就是說:

  • everysec設定最多可能丟失2秒資料,不是1秒。
  • 如果系統fsync緩慢,將會導致Redis主執行緒阻塞影響效率。

到此這篇關於一文詳解Redis中的持久化的文章就介紹到這了,更多相關Redis持久化內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com