<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  • Redis系列2:數據持久化提高可用性

    1 介紹

    從上一篇的 《深刻理解高性能Redis的本質》 中可以知道, 我們經常在數據庫層上加一層緩存(如Redis),來保證數據的訪問效率。
    這樣性能確實也有了大幅度的提升,但是本身Redis也是一層服務,也存在宕機、故障的可能性。
    一旦服務掛起,可能生產的后果包括如下幾方面:
    1、Redis的數據是存在內存中的,所以一旦掛起,內存中的數據會全部丟失。
    2、I/O從內存層級遷移到磁盤層級,性能極速下降。
    3、原本訪問緩存的請求會透過緩存層直接投向數據庫,給數據庫帶來極大的壓力,甚至導致雪崩。

    所以,緩存層崩潰產生的后果是災難的。為了避免宕機和宕機后的數據丟失, 為了保證數據的快速恢復,Redis提供了兩個持久化數據的能力, AOF(Append Only FIle)日志 和 RDB 快照。

    2 關于RDB 內存快照

    大規模高并發的分布式場景,經常會遇到問題就是Redis掛起,導致訪問失敗,而所有的請求透過緩存層投向數據庫,給數據庫造成極大的壓力。
    而Redis的數據是存儲在高速緩存中,即使我們重啟并且恢復使用,緩存池依舊是空的,因為內存被釋放了。
    重新建立緩存的過程,對數據庫也是一個暴擊的過程,很可能會導致整個系統調用鏈的雪崩。參考我的這篇《架構與思維:一次緩存雪崩的災難復盤
    我們知道,Redis 數據都是保存在內存中,能不能將內存中的數據進一步寫到磁盤上,Redis 重啟的時候就可以把磁盤上的數據快速恢復到內存中。這樣,即使Redis宕機重啟之后,依然能夠正常的提供服務。
    但是不能忽略一個問題,Redis和MySQL最大的區別之一就是一個存儲在內存,一個持久化在磁盤。但是如果每次數據的變化(新增、修改、刪除緩存)都要寫內存并同時寫磁盤,這樣成本太高,內存+磁盤,會讓 Redis 性能大大降低。而且還要保證原子性操作,避免內存和磁盤的數據不一致。

    2.1 使用內存快照

    為了避免實時寫入高頻操作磁盤帶來的負面效應。Redis提供了內存快照策略。
    我們知道,Redis 在 執行寫(增、刪、改)指令過程中,內存中數據會持續的在變化。而內存快照,指的是 Redis 內存中的數據在某一刻的狀態。就好比如是拍照一樣,你把那一刻的數據都定格下來,持久化到磁盤上。打游戲的同學可以想象存盤。
    快照文件我們稱之為 RDB 文件,即 Redis DataBase 的縮寫。
    Redis 通過定時執行 RDB 內存快照,這樣就不必每次執行寫指令都存盤,只需要在執行內存快照的時候寫磁盤。這樣既保證Redis的高效讀寫,還實現了定時持久化,宕機后可快速恢復數據。
    image
    如上圖,在做數據恢復時,直接將 RDB 文件讀入內存完成恢復。

    2.2 生成RDB策略

    Redis 提供了兩種模式來生成 RDB 文件:

    • save: 由主線程來執行,同步阻塞,只有等save完成后,才能進行新操作;
    • bgsave:執行后,會立刻返回OK,同時調用 glibc 的函數fork產生一個子進程用于寫入 RDB 文件,快照持久化完全交給子進程來處理。主進程繼續執行他自己的工作,非阻塞。

    2.2.1 save模式

    save模式是主進程執行,非常不建議使用主進程執行的方式,在 《深刻理解高性能Redis的本質》 中,
    我們知道他的主操作都是在單線程模型上完成的。所以盡量避免 RDB 文件生成影響主線程的網絡I/O和鍵值對讀寫。

    2.2.2 bgsave模式

    上面提到的另外一種方式,fork一個子進程來寫RDB文件。
    Redis 使用操作系統的多進程寫時復制技術 COW(Copy On Write) 來實現快照持久化,這個很重要,具體可以了解下這篇《Copy On Write機制》,寫的不錯。
    Redis 在持久化時會調用 glibc 的函數fork產生一個子進程,由這個子進程來處理快照持久化的動作,子進程可以共享主進程的所有內存數據,所以它讀取到主進程的數據之后寫入到 RDB 文件。而父進程繼續處理客戶端的寫操作,不受影響。
    在創建 RDB 文件時,程序會對數據庫中的鍵進行檢查,僅僅將未過期的鍵保存到新創建的 RDB 文件中。
    當主進程執行寫指令修改數據的時候,這個數據就會復制一份副本, bgsave 子進程讀取這個副本數據寫到 RDB 文件,所以主進程就可以直接修改原來的數據。
    image
    這既保證了快照的完整性,也允許主進程同時對數據進行修改,避免了對正常業務的影響。

    2.2.3 避免過頻全量照片

    雖然說Redis 使用 bgsave 函數 fork 子進程在后臺完成 內存中的數據做快照,沒有影響父進程繼續處理客戶端的各種操作。
    但是需注意一點,過于頻繁的執行全量的數據快照,必然會導致嚴重的性能開銷:

    • 頻繁生成 RDB 文件寫入磁盤,磁盤壓力過大,效率降低。
    • fork 出來的 bgsave 子進程因為共享主線程的數據,一定程度上會阻塞主線程的運行,主線程的內存越大,阻塞時間越長。

    2.3 總結

    • 快照的恢復速度快,但是生成 RDB 文件的頻率需要把握一個度,頻率過低快照間隔數據較大,丟失的數據就會比較多;頻率太快,又會消耗額外開銷,降低Redis性能。
    • RDB 建議采用二進制 + 數據壓縮的方式寫磁盤,文件體積小,數據恢復速度快。

    3 AOF 日志

    AOF 日志存儲了 Redis 服務器的順序指令序列,AOF 日志只記錄對內存進行修改的指令記錄。
    假設 AOF 日志記錄了自 Redis 實例創建以來所有的修改性指令序列,那么就可以通過對一個空的 Redis 實例順序執行所有的指令。
    也就是說,可以通過重放(replay),來建立 Redis 當前實例的內存數據結構。這種模式有沒有很熟悉,有沒有想到MySQL主從同步時候的relay log。

    3.1 日志變更前后對比

    AOF記錄日志有兩種模式,一種是預寫式日志,也稱寫前日志(Write Ahead Log, WAL): 在實際寫數據之前,將修改的數據寫到日志文件中。
    另外一種是寫后日志: 先執行寫操作,當數據存入內存后,再記錄日志。
    預寫式日志類似 MySQL Innodb 引擎 中的 redo log,修改數據前先記錄日志,再修改。
    image

    3.2 日志格式

    Redis 接收到 set keyName someValue 命令的時候,會先將數據寫到內存,Redis 會按照如下格式寫入 AOF 文件。
    *3:表示當前指令分為三個部分,每個部分都是 $ + 數字開頭,后面是3部分的具體內容:指令、鍵、值。
    數字:表示這部分的命令、鍵、值多占用的字節大小。比如 $3表示這部分包含 3 個字符,也就是 set 的長度。
    image

    推薦使用寫后日志的模式,避免了額外的檢查開銷,不需要對執行的命令進行語法檢查。如果使用寫前日志的話,就需要先檢查語法是否有誤,否則日志記錄了錯誤的命令,在使用日志恢復的時候就會出錯。另外,寫后才記錄日志,不會阻塞當前的 指令執行。

    # set keyName someValue
    *3
    $3
    set
    $7  #長度為7
    keyName
    $9 #長度為9
    someValue
    
    # 執行 mset key1 1 ,key2 2 ,key33 3
    # aof日志如下:
    *7  # 本批命令需要往下讀7行非 $ 開始的命令
    $4  #接著讀取4個字節寬度,‘mset’長度為4,記為 $4
    mset
    $4  #接著讀取4個字節寬度,‘key1’長度為4,記為 $4
    key1
    $1  #接著讀取1個字節寬度,‘1’長度為1,記為 $1
    1
    $4
    key2
    $1
    2
    $5  #接著讀取的字節寬度,‘$key33’長度為5,記為 $5
    key33
    $1
    3
    

    3.3 可能存在的問題

    • 可能存在丟失:比如Redis 剛執行完指令,還沒記錄日志宕機了,命令數據就丟了。
    • AOF 避免了當前命令的阻塞,但是AOF 日志是主線程執行,將日志寫入磁盤過程中,如果磁盤壓力大就會導致執行變慢,降低后續的操作。

    3.4 寫回策略

    上面的問題,在Redis高頻讀寫的時候是必然存在的,想要解決,在寫入的時候做一層緩沖就可以了,避免直塞。這時候Redis提供了一種執行策略叫寫回策略。

    3.4.1 寫回策略說明

    為了提高日志文件的寫入效率,寫回策略會做如下變化:

    • 當你調用 write 函數將數據寫入到文件時,這時候不是真正的落盤,而是將寫入數據暫存在操作系統的內存緩沖區里。
    • 待到緩沖區的空間被填滿、或者超過了指定的閾值時候,才真正地將緩沖區中的數據寫入到磁盤里面。
      這種做法顯然提高了效率,但也為寫入數據帶來了安全性問題,如果服務器發生了單機,那么保存在內存緩沖區里面的寫入數據就會丟失。
      為此,系統提供了fsyncfdatasync兩個同步函數,它們可以強制讓操作系統立即將緩沖區中的數據寫入到硬盤里面,從而確保寫入數據的安全性。
      Redis 提供的 AOF 配置項 appendfsync 寫回策略直接決定 AOF 持久化功能的效率和安全性,以下是 appendfsync 的3個枚舉:
    • always:同步寫回,寫指令執行完 即將緩沖區內容回寫到 AOF 文件。
    • everysec:每秒寫回,寫指令執行完,日志寫到 AOF 文件緩沖區,緩沖區每隔一秒再把內容同步到磁盤。
    • no: 操作系統控制,寫執行執行完畢,把日志寫到 AOF 文件內存緩沖區,由操作系統決定何時回寫到磁盤。

    寫磁盤會帶來性能上的損耗,所以寫回的策略要根據實際情況做一個取舍,比如你是偏向性能還是可靠性。
    always 同步寫回可以做到數據不丟失,但是每次執行寫指令都需要寫入磁盤,性能最差。
    everysec 每秒寫回,避免了同步寫回的性能開銷,但是如果服務發生宕機,會有大約1s時間周期的數據丟失,這種模式是在性能和可靠性之間做了妥協。
    no 操作系統控制,執行寫指令后就寫入 AOF 文件緩沖,再執行后續的寫磁盤指令,性能最好,但有可能丟失更多的數據。

    3.4.2 寫回策略的選擇

    我們可以根據服務的實際情況來抉擇策略,看是偏向高性能還是高可靠。

    • 高性能需求,選擇 No 策略
    • 高可靠性保證,就選擇 Always 策略
    • 如果能夠接受數據存在少量丟失,又希望性能較好的話,就選擇 Everysec 策略

    4 混合RDF/AOF 方式模式

    現實情況下,無論使用RDB或者AOF都差點意思。使用 rdb 來恢復內存狀態,勢必會丟失一部分數據。 使用 AOF 日志重放,重放對性能有一定的影響,而且在 Redis 實例很大的情況下,需要花費很長的時間。
    Redis 4.0 解決了這個問題,才用了一個新的持久化模式——混合持久化,該 混合模式 默認是關閉狀態的。
    將 RDB 文件的內容和 rdb快照時間點之后的增量的 AOF 日志文件存在一起。這時候 AOF 日志不需要再是全量的日志,而是最近一次快照時間點之后到當下發生的增量 AOF 日志,通常這部分 AOF 日志很小。
    所以執行有如下順序:

    • 查找rdb內容,如果存在先加載 rdb內容再 重放剩余的 aof。
    • 沒有rdb內容,直接以aof格式重放整個文件。
      這樣快照就不用頻繁的執行,同時由于 AOF 只需要記錄最近一次快照之后的數據,不需要記錄所有的操作,避免了出現單次重放文件過大的問題。
      image

    5 總結

    • RDB提供了快照模式,記錄某個時間的Redis內存狀態。RDB設計了 bgsave 和寫時復制,盡可能避免執行快照期間對讀寫指令的影響,但是頻繁快照會給磁盤帶來壓力以及 fork 阻塞主線程。需把握頻率。
    • AOF 日志存儲了 Redis 服務的順序指令序列,通過重放(replay)指令來寫入日志文件,并通過寫回策略來避免高頻讀寫給Redis帶來壓力。
    • RDB快照的照片時間間隔,必然會帶來數據缺失,如果允許分鐘級別的數據丟失,可以只使用 RDB。
    • 如果只用 AOF,寫回策略優先使用 everysec 的配置選項,因為它在可靠性和性能之間取了一個平衡。
    • 數據不能丟失時,內存快照和 AOF 的混合使用是一個很好的選擇。
    posted @ 2022-06-27 15:20  Hello-Brand  閱讀(336)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看