redis系列(六)—持久化機制

redis持久化機制

前言

大家好,牧碼心今天給大家推薦一篇redis系列(六)—持久化機制的文章,在實際工作中有很多應用場景,希望對你有所幫助。內容如下:

  • 持久化概述
  • RDB持久化
  • AOF持久化
  • 持久化方案對比
  • 常見問題

持久化概述

redis持久化是指將在內存的數據同步或異步寫到磁盤中,並永久性保存。支持RDB和AOF兩種持久化機制,持久化功能有效地避免因進程退出造成的數據丟失問題,當下次重啓時利用之前持久化的文件即可實現數據恢復。除此之外,爲了進行災難備份,可以將持久化文件拷貝到一個遠程位置。

RDB持久化

  • 定義

    RDB持久化是將當前進程中的數據生成快照保存到硬盤(因此也稱作快照持久化),保存的文件後綴是rdb;當Redis重新啓動時,可以讀取快照文件恢復數據。觸發RDB持久化過程分爲手動觸發和自動觸發。

  • 手動觸發
    手動觸發分別對應save和bgsave命令:
    save命令:阻塞當前Redis服務器, 直到RDB過程完成爲止, 對於內存比較大的實例會造成長時間阻塞, 線上環境不建議使用。例如:

    127.0.0.1:6379> save
    OK
    (1.31s)
    

    bgsave命令:會創建一個子進程,由子進程來負責創建RDB文件,父進程(即Redis主進程)則繼續處理請求。例如:

    127.0.0.1:6379> bgsave
    Background saving started
    

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

  • 自動觸發
    除了執行命令手動觸發之外,Redis內部還存在自動觸發RDB的持久化機制,例如以下場景都會觸發自動執行:
    1.使用save相關配置, 如“save m n”。表示m秒內數據集存在n次修改時,自動觸發bgsave;
    2.執行debug reload命令重新加載Redis時, 也會自動觸發save操作;
    3.默認情況下執行shutdown命令時, 如果沒有開啓AOF持久化功能則自動執行bgsave。

    下面對第一種場景進行說明:

    save m n
    

    例如,查看redis的默認配置文件(redis),可以看到如下配置信息:
    RDB持久化配置
    其中save 900 1的含義是:當時間到900秒時,如果redis數據發生了至少1次變化,則執行bgsave;save 300 10和save 60 10000同理。當三個save條件滿足任意一個時,都會引起bgsave的調用。

  • save m n 實現原理
    Redis的save m n,是通過serverCron函數、dirty計數器、和lastsave時間戳來實現的。
    serverCron是Redis服務器的週期性操作函數,默認每隔100ms執行一次;該函數對服務器的狀態進行維護,其中一項工作就是檢查 save m n 配置的條件是否滿足,如果滿足就執行bgsave。
    dirty計數器是Redis服務器維持的一個狀態,記錄了上一次執行bgsave/save命令後,服務器狀態進行了多少次修改(包括增刪改);而當save/bgsave執行完成後,會將dirty重新置爲0。
    例如,如果Redis執行了set mykey helloworld,則dirty值會+1;如果執行了sadd myset v1 v2 v3,則dirty值會+3;注意dirty記錄的是服務器進行了多少次修改,而不是客戶端執行了多少修改數據的命令。
    lastsave時間戳也是Redis服務器維持的一個狀態,記錄的是上一次成功執行save/bgsave的時間。
    save m n的原理如下:每隔100ms,執行serverCron函數;在serverCron函數中,遍歷save m n配置的保存條件,只要有一個條件滿足,就進行bgsave。對於每一個save m n條件,只有下面兩條同時滿足時纔算滿足:

    (1)當前時間-lastsave > m

    (2)dirty >= n
    下圖是save m n觸發bgsave執行時,服務器打印日誌的情況:
    在這裏插入圖片描述

  • 執行流程
    bgsave是主流的觸發RDB持久化方式, 下面根據下圖瞭解它的運作流程。
    RDB持久化執行流程圖
    從上圖可以看出整個流程可分爲如下幾個步驟:

  1. Redis父進程首先判斷:當前是否在執行save,或bgsave/bgrewriteaof的子進程,如果在執行則bgsave命令直接返回。(bgsave/bgrewriteaof 的子進程不能同時執行,主要是基於性能方面的考慮:兩個併發的子進程同時執行大量的磁盤寫操作,可能引起嚴重的性能問題;)

  2. 父進程執行fork操作創建子進程,這個過程中父進程是阻塞的,Redis不能執行來自客戶端的任何命令;

  3. 父進程fork後,bgsave命令返回”Background saving started”信息並不再阻塞父進程,並可以響應其他命令;

  4. 子進程創建RDB文件,根據父進程內存快照生成臨時快照文件,完成後對原有文件進行原子替換;

  5. 子進程發送信號給父進程表示完成,父進程更新統計信息;

  • RDB文件

    • 存儲路徑:RDB文件保存在dir配置指定的目錄下, 文件名通過dbfilename配置指定。可以通過執行config set dir{newDir}和config set dbfilename{newFileName}運行期動態執行,當下次運行時RDB文件會保存到新目錄;
      當遇到壞盤或磁盤寫滿等情況時, 可以通過config set dir{newDir}在線修改文件路徑到可用的磁盤路徑, 之後執行bgsave進行磁盤切換, 同樣適用於AOF持久化文件。

      127.0.0.1:6379> config set dir /data/db/redis
      
    • 壓縮
      Redis默認採用LZF算法對生成的RDB文件做壓縮處理, 壓縮後的文件遠遠小於內存大小, 默認開啓, 可以通過參數config setrdbcompression{yes|no}動態修改。如:

      127.0.0.1:6379> CONFIG SET rdbcompression yes
      	OK
      

      雖然壓縮RDB會消耗CPU, 但可大幅降低文件的體積, 方便保存到硬盤或通過網絡發送給從節點, 因此線上建議開啓。

  • 啓動時加載
    RDB文件的載入工作是在服務器啓動時自動執行的,並沒有專門的命令。但是由於AOF的優先級更高,因此當AOF開啓時,Redis會優先載入AOF文件來恢復數據;只有當AOF關閉時,纔會在Redis服務器啓動時檢測RDB文件,並自動載入。服務器載入RDB文件期間處於阻塞狀態,直到載入完成爲止。
    Redis載入RDB文件時,會對RDB文件進行校驗,如果文件損壞,則日誌中會打印錯誤,Redis啓動失敗。

  • RDB常用配置總結
    下面是RDB常用的配置項,以及默認值;

    save m n:bgsave自動觸發的條件;如果沒有save m n配置,相當於自動的RDB持久化關閉,不過此時仍可以通過其他方式觸發
    
    stop-writes-on-bgsave-error yes:當bgsave出現錯誤時,Redis是否停止執行寫命令;設置爲yes,則當硬盤出現問題時,可以及時發現,避免數據的大量丟失;設置爲no,則Redis無視bgsave的錯誤繼續執行寫命令,當對Redis服務器的系統(尤其是硬盤)使用了監控時,該選項考慮設置爲no
    
    rdbcompression yes:是否開啓RDB文件壓縮
    
    rdbchecksum yes:是否開啓RDB文件的校驗,在寫入文件和讀取文件時都起作用;關閉checksum在寫入文件和啓動文件時大約能帶來10%的性能提升,但是數據損壞時無法發現
    
    dbfilename dump.rdb:RDB文件名
    
    dir ./:RDB文件和AOF文件所在目錄
    

AOF 持久化

  • 定義

    RDB持久化是將數據寫入文件,AOF的持久化則是將redis的每次執行的命令記錄寫到日誌文件中(類似mysql的binlog)。當redis重啓時再次執行AOF文件中命令進行恢復

  • 開啓AOF
    Redis服務器默認開啓RDB,關閉AOF;要開啓AOF,AOF文件名通過appendfilename配置設置, 默認文件名是appendonly.aof。保存路徑同RDB持久化方式一致, 通過dir配置指定:

    appendonly yes
    
  • 執行流程
    AOF的工作流程操作: 命令寫入(append)、文件同步(sync)、文件重寫(rewrite)、重啓加載(load),如圖所示:
    AOF持久化執行流程
    從上圖可以看出整個流程可分爲如下幾個步驟:
    1) 所有的寫入命令會追加到aof_buf( 緩衝區) 中。
    2) AOF緩衝區根據對應的策略向硬盤做同步操作。
    3) 隨着AOF文件越來越大,需要定期對AOF文件進行重寫,達到壓縮的目的。
    4) 當Redis服務器重啓時,可以加載AOF文件進行數據恢復。

    瞭解AOF工作流程之後, 下面針對每個步驟做詳細介紹:

    • 命令寫入

      • AOF 命令寫入的內容直接是文本協議格式,例如set hello world 命令,在AOF 緩衝區會追加如下文本:
      *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
      
      • AOF 採用文本協議格式,這樣具有更好的兼容性,可讀性強,方便處理和避免二次處理開銷;
      • AOF 命令會寫入緩衝區aof buff,是因爲Redis使用單線程響應命令, 如果每次寫AOF文件命令都直接追加到硬盤, 那麼性能完全取決於當前硬盤負載。另一方面是Redis可以提供多種緩衝區同步硬盤的策略, 在性能和安全性方面做出平衡。
    • 文件同步
      Redis提供了多種AOF緩存區的同步文件策略,策略涉及到操作系統的write函數和fsync函數,說明如下:

      • write函數會觸發延遲寫機制,用戶線程調用write函數先將數據寫入緩衝區後直接返回,當緩衝區滿或超過指定時限後。系統會由專門的線程進行刷盤操作。這樣提高了寫入效率,但也帶來不安全性。
      • 同步文件之前, 如果此時系統故障宕機, 緩衝區內數據將丟失。因此係統同時提供了fsync、fdatasync等同步函數,可以強制操作系統立刻將緩衝區中的數據寫入到硬盤裏,從而確保數據的安全性。
      • Redis提供了多種AOF緩衝區同步文件策略, 由參數appendfsync控制,不同值的含義如下:
        • always: 命令寫入aof buff 後調用系統fsync 操作同步到AOF文件,fsync 完成後線程返回;這種情況下,每次有寫命令都要同步到AOF文件,硬盤IO成爲性能瓶頸,Redis只能支持大約幾百TPS寫入,嚴重降低了Redis的性能;
        • everysec:命令寫入aof_buf後調用系統write操作,write完成後線程返回;fsync同步文件操作由專門的線程每秒調用一次。everysec是前述兩種策略的折中,是性能和數據安全性的平衡,因此是Redis的默認配置,也是我們推薦的配置。
        • no:命令寫入aof_buf後調用系統write操作,不對AOF文件做fsync同步;同步由操作系統負責,通常同步週期爲30秒。這種情況下,文件同步的時間不可控,且緩衝區中堆積的數據會很多,數據安全性無法保證。
    • 文件重寫
      隨着命令不斷寫入AOF, 文件會越來越大, 爲了解決這個問題, Redis引入AOF重寫機制壓縮文件體積。 AOF文件重寫是把Redis進程內的數據轉化爲寫命令同步到新AOF文件的過程。文件重寫之所以能夠壓縮AOF文件,原因在於:

      • 過期的數據不再寫入文件;
      • 無效的命令不再寫入文件:如有些數據被重複設值(set mykey v1, set mykey v2)、有些數據被刪除了(sadd myset v1, del myset)等等;
      • 多條命令可以合併爲一個:如sadd myset v1, sadd myset v2, sadd myset v3可以合併爲sadd myset v1 v2 v3。不過爲了防止單條命令過大造成客戶端緩衝區溢出,對於list、set、hash、zset類型的key,並不一定只使用一條命令;而是以某個常量爲界將命令拆分爲多條。這個常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定義,不可更改,3.0版本中值是64。
    • 文件重寫觸發
      AOF重寫過程可以手動觸發和自動觸發:

      • 手動觸發:直接調用bgrewriteaof命令,該命令的執行與bgsave有些類似:都是fork子進程進行具體的工作,且都只有在fork時阻塞。
        在這裏插入圖片描述

      • 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數,以及aof_current_size和aof_base_size狀態確定觸發時機。

        • auto-aof-rewrite-min-size:執行AOF重寫時,文件的最小體積,默認值爲64MB。
        • auto-aof-rewrite-percentage:執行AOF重寫時,當前AOF大小(即aof_current_size)和上一次重寫時AOF大小(aof_base_size)的比值。
          其中,參數可以通過config get命令查看:
          在這裏插入圖片描述
          只有當auto-aof-rewrite-min-size和auto-aof-rewrite-percentage兩個參數同時滿足時,纔會自動觸發AOF重寫,即bgrewriteaof操作。
      • 文件重寫的流程
        AOF持久化重寫流程圖
        對照上圖,文件重寫的流程如下:

    1. Redis父進程首先判斷當前是否存在正在執行 bgsave/bgrewriteaof的子進程,如果存在則bgrewriteaof命令直接返回,如果存在bgsave命令則等bgsave執行完成後再執行。前面曾介紹過,這個主要是基於性能方面的考慮。

    2. 父進程執行fork操作創建子進程,這個過程中父進程是阻塞的。

    3. 父進程fork後,bgrewriteaof命令返回”Background append only file rewrite started”信息並不再阻塞父進程,並可以響應其他命令。Redis的所有寫命令依然寫入AOF緩衝區,並根據appendfsync策略同步到硬盤,保證原有AOF機制的正確。

    4. 由於fork操作使用寫時複製技術,子進程只能共享fork操作時的內存數據。由於父進程依然在響應命令,因此Redis使用AOF重寫緩衝區(圖中的aof_rewrite_buf)保存這部分數據,防止新AOF文件生成期間丟失這部分數據。也就是說,bgrewriteaof執行期間,Redis的寫命令同時追加到aof_buf和aof_rewirte_buf兩個緩衝區。

    5. 子進程根據內存快照,按照命令合併規則寫入到新的AOF文件。

    6. 子進程寫完新的AOF文件後,向父進程發信號,父進程更新統計信息,具體可以通過info persistence查看。

    7. 父進程把AOF重寫緩衝區的數據寫入到新的AOF文件,這樣就保證了新AOF文件所保存的數據庫狀態和服務器當前狀態一致。

    8. 使用新的AOF文件替換老文件,完成AOF重寫。

    • 重啓加載(load)
      redis啓動時從持久化文件中加載數據,加載過程如圖:
      redis啓動加載持久化數據流程
      從上圖可以看出整個流程可分爲如下幾個步驟:

        1) AOF持久化開啓且存在AOF文件時, 優先加載AOF文件;
        
        2) AOF關閉或者AOF文件不存在時, 加載RDB文件;
        
        3) 加載AOF/RDB文件成功後, Redis啓動成功。
        
        4) AOF/RDB文件存在錯誤時, Redis啓動失敗並打印錯誤信息。
      
      • 文件校驗
        與載入RDB文件類似,Redis載入AOF文件時,會對AOF文件進行校驗,如果文件損壞,則日誌中會打印錯誤,Redis啓動失敗。但如果是AOF文件結尾不完整(機器突然宕機等容易導致文件尾部不完整),且aof-load-truncated參數開啓,則日誌中會輸出警告,Redis忽略掉AOF文件的尾部,啓動成功。aof-load-truncated參數默認是開啓的:當遇到此問題時會忽略並繼續啓動, 同時打印如下警告日誌:
        !!! Warning: short read while loading the AOF file !!!
        	!!! Truncating the AOF at offset 397856725 !!!
        	AOF loaded anyway because aof-load-truncated is enabled
        
  • AOF常用配置總結
    下面是AOF常用的配置項,以及默認值;前面介紹過的這裏不再詳細介紹。

    appendonly no:是否開啓AOF
    appendfilename "appendonly.aof":AOF文件名
    dir ./:RDB文件和AOF文件所在目錄
    appendfsync everysec:fsync持久化策略
    no-appendfsync-on-rewrite no:AOF重寫期間是否禁止fsync;如果開啓該選項,可以減輕文件重寫時CPU和硬盤的負載(尤其是硬盤),但是可能會丟失AOF重寫期間的數據;需要在負載和安全性之間進行平衡
    auto-aof-rewrite-percentage 100:文件重寫觸發條件之一
    auto-aof-rewrite-min-size 64mb:文件重寫觸發提交之一
    aof-load-truncated yes:如果AOF文件結尾損壞,Redis啓動時是否仍載入AOF文件;
    

持久化方案對比

  • RDB和AOF的優缺點

    • RDB持久化

      優點:RDB文件緊湊,體積小,網絡傳輸快,適合全量複製;恢復速度比AOF快很多。當然,與AOF相比,RDB最重要的優點之一是對性能的影響相對較小。

      缺點:RDB文件的致命缺點在於其數據快照的持久化方式決定了必然做不到實時持久化,而在數據越來越重要的今天,數據的大量丟失很多時候是無法接受的,因此AOF持久化成爲主流。此外,RDB文件需要滿足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

    • AOF持久化

      優點:支持秒級持久化、兼容性好;
      缺點:文件大、恢復速度慢、對性能影響大。

  • 持久化策略選擇
    在介紹持久化策略之前,首先要明白無論是RDB還是AOF,持久化的開啓都是要付出性能方面代價的:對於RDB持久化,一方面是bgsave在進行fork操作時Redis主進程會阻塞,另一方面,子進程向硬盤寫數據也會帶來IO壓力;對於AOF持久化,向硬盤寫數據的頻率大大提高(everysec策略下爲秒級),IO壓力更大,甚至可能造成AOF追加阻塞問題(後面會詳細介紹這種阻塞),此外,AOF文件的重寫與RDB的bgsave類似,會有fork時的阻塞和子進程的IO壓力問題。相對來說,由於AOF向硬盤中寫數據的頻率更高,因此對Redis主進程性能的影響會更大。
    在實際生產環境中,根據數據量、應用對數據的安全要求、預算限制等不同情況,會有各種各樣的持久化策略;如完全不使用任何持久化、使用RDB或AOF的一種,或同時開啓RDB和AOF持久化等。此外,持久化的選擇必須與Redis的主從策略一起考慮,因爲主從複製與持久化同樣具有數據備份的功能,而且主機master和從機slave可以獨立的選擇持久化方案。
    下面分場景來討論持久化策略的選擇,下面的討論也只是作爲參考,實際方案可能更復雜更具多樣性。

    • 數據丟失可接受,可以都關閉持久化

    • 主從環境在,slave的存在既可以實現數據的熱備,也可以進行讀寫分離分擔Redis讀請求,以及在master宕掉後繼續提供服務。

      在這種情況下,一種可行的做法是:

      • master:完全關閉持久化(包括RDB和AOF),這樣可以讓master的性能達到最好

      • slave:關閉RDB,開啓AOF(如果對數據安全要求不高,開啓RDB關閉AOF也可以),並定時對持久化文件進行備份(如備份到其他文件夾,並標記好備份的時間);然後關閉AOF的自動重寫,然後添加定時任務,在每天Redis閒時(如凌晨12點)調用bgrewriteaof。

      這裏需要解釋一下,爲什麼開啓了主從複製,可以實現數據的熱備份,還需要設置持久化呢?因爲在一些特殊情況下,主從複製仍然不足以保證數據的安全,例如:
      1.master和slave進程同時停止:考慮這樣一種場景,如果master和slave在同一棟大樓或同一個機房,則一次停電事故就可能導致master和slave機器同時關機,Redis進程停止;如果沒有持久化,則面臨的是數據的完全丟失。
      2.master誤重啓:考慮這樣一種場景,master服務因爲故障宕掉了,如果系統中有自動拉起機制(即檢測到服務停止後重啓該服務)將master自動重啓,由於沒有持久化文件,那麼master重啓後數據是空的,slave同步數據也變成了空的;如果master和slave都沒有持久化,同樣會面臨數據的完全丟失。

    • 異地災備:上述討論的幾種持久化策略,針對的都是一般的系統故障,如進程異常退出、宕機、斷電等,這些故障不會損壞硬盤。但是對於一些可能導致硬盤損壞的災難情況,如火災地震,就需要進行異地災備。例如對於單機的情形,可以定時將RDB文件或重寫後的AOF文件,通過scp拷貝到遠程機器,如阿里雲、AWS等;對於主從的情形,可以定時在master上執行bgsave,然後將RDB文件拷貝到遠程機器,或者在slave上執行bgrewriteaof重寫AOF文件後,將AOF文件拷貝到遠程機器上。一般來說,由於RDB文件文件小、恢復快,因此災難恢復常用RDB文件;異地備份的頻率根據數據安全性的需要及其他條件來確定,但最好不要低於一天一次。

常見問題

  • fork 操作阻塞
    當Redis做RDB或AOF重寫時,一個必不可少的操作就是執行fork操作創建子進程,對於大多數操作系統來說fork是個重量級錯誤。 雖然fork創建的子進程不需要拷貝父進程的物理內存空間,但是會複製父進程的空間內存頁表。
    例如:對於高流量的Redis實例OPS可達5萬以上,如果fork操作耗時在秒級別將拖慢Redis幾萬條命令執行, 對線上應用延遲影響非常明顯。正常情況下fork耗時應該是每GB消耗20毫秒左右。 可以在info stats統計中查latest_fork_usec指標獲取最近一次fork操作耗時,單位微秒
    如何優化fork操作耗時:
    1) 優先使用物理機或者高效支持fork操作的虛擬化技術、避免使用Xen;
    2) 控制Redis實例最大可用內存,fork耗時跟內存量成正比, 線上建議每個Redis實例內存控制在10GB以內;
    3) 合理配置Linux內存分配策略、避免物理內存不足導致fork失敗;
    4) 降低fork操作的頻率, 如適度放寬AOF自動觸發時機,避免不必要的全量複製等。

  • cpu消耗阻塞
    子進程負責把進程內的數據分批寫入文件,這個過程屬於CPU密集操作, 通常子進程對單核CPU利用率接近90%.由於子進程非常消耗CPU,會和父進程產生單核資源競爭。不要和其他CPU密集型服務部署在一起,造成CPU過度競爭。

  • 硬盤消耗阻塞
    子進程主要職責是把AOF或者RDB文件寫入硬盤持久化。 勢必造成硬盤寫入壓力。 根據Redis重寫AOF/RDB的數據量, 結合系統工具如sar、iostat、 iotop等,可分析出重寫期間硬盤負載情況。

    對於硬盤開銷優化。 優化方法如下:
    a) 不要和其他高硬盤負載的服務部署在一起。 如: 存儲服務、 消息隊列服務等
    b) AOF重寫時會消耗大量硬盤IO, 可以開啓配置no-appendfsync-on-rewrite, 默認關閉。 表示在AOF重寫期間不做fsync操作。
    c) 當開啓AOF功能的Redis用於高流量寫入場景時, 如果使用普通機械磁盤,寫入吞吐一般在100MB/s左右, 這時Redis實例的瓶頸主要在AOF同步硬盤上。
    d) 對於單機配置多個Redis實例的情況, 可以配置不同實例分盤存儲AOF文件, 分攤硬盤寫入壓力。

參考

  • 《Redis開發與運維》
  • https://www.cnblogs.com/kismetv/p/9137897.html#t4
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章