前言
大家好,我是小卷聊開發
?如果有人問你:"你會把 Redis 用在什么業務場景下?"
我想你大概率會說:"我會把它當作快取使用,因為它把后端資料庫中的資料存盤在記憶體中,然后直接從記憶體中讀取資料,回應速度會非常快,"
沒錯,這確實是 Redis 的一個普遍使用場景,但是,這里也有一個絕對不能忽略的問題:「一旦服務器宕機,記憶體中的資料將全部丟失」,
目前,Redis 的持久化主要有兩大機制,即「AOF(Append Only File)日志和 RDB(Redis DataBase) 快照」,
1.AOF
日志是如何實作的
說到日志,我們比較熟悉的是資料庫的寫前日志(Write Ahead Log, WAL),在實際寫資料前,先把修改的資料記到日志檔案中,以便故障時進行恢復,不過,AOF 日志正好相反,它是寫后日志,"寫后"的意思是 Redis 是先執行命令,把資料寫入記憶體,然后才記錄日志,

AOF 里記錄的是 Redis 收到的每一條命令,這些命令是以文本形式保存的,
我們以 Redis 收到“set testkey testvalue”命令后記錄的日志為例,看看 AOF 日志的內容,其中,“*3”表示當前命令有三個部分,每部分都是由“數字開頭,后面緊跟著具體的命令、鍵或值,這里,數字表示這部分中的命令、鍵或值一共有多少位元組,例如,3 set”表示這部分有 3 個位元組,也就是“set”命令,

寫后日志的優勢與風險
「為了避免額外的檢查開銷,Redis 在向 AOF 里面記錄日志的時候,并不會先去對這些命令進行語法檢查」,
如果先記日志再執行命令的話,日志中就有可能記錄了錯誤的命令,Redis 在使用日志恢復資料時,就可能會出錯,而寫后日志這種方式,就是先讓系統執行命令,只有命令能執行成功,才會被記錄到日志中,否則,系統就會直接向客戶端報錯,
所以,Redis 使用寫后日志這一方式的一大好處是,可以避免出現記錄錯誤命令的情況,
除此之外,寫后日志一個好處:它是在命令執行后才記錄日志,「不會阻塞當前的寫操作」,
AOF 也有兩個潛在的風險:
- 風險一:如果剛執行完一個命令,還沒有來得及記日志就宕機了,那么這個命令和相應的資料就有丟失的風險,
- 如果此時 Redis 是用作快取,還可以從后端資料庫重新讀入資料進行恢復,
- 如果Redis是直接用作資料庫的話,此時,因為命令沒有記入日志,所以就無法用日志進行恢復了,
- 風險二:AOF雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險,
- AOF 日志也是在主執行緒中執行(寫回策略為 always時),如果在把日志檔案寫入磁盤時,磁盤寫壓力大,就會導致寫盤很慢,進而導致后續的操作也無法執行了,
這兩個風險都是和 AOF 寫回磁盤的時機相關的,這也就意味著,如果我們能夠控制一個寫命令執行完后 AOF 日志寫回磁盤的時機,這兩個風險就解除了,
2.日志的寫回策略
AOF 機制一共有三種寫回策略,也就是 AOF 配置項appendfsync的三個可選值,
-
「Always 同步寫回」:每個寫命令執行完,立馬同步地將日志寫回磁盤;
-
「Everysec每秒寫回」:每個寫命令執行完,只是先把日志寫到 AOF 檔案的記憶體緩沖區,每隔一秒把緩沖區中的內容寫入磁盤;
-
「No作業系統控制的寫回」:每個寫命令執行完,只是先把日志寫到 AOF 檔案的記憶體緩沖區,由作業系統決定何時將緩沖區內容寫回磁盤,
針對避免主執行緒阻塞和減少資料丟失問題,這三種寫回策略都無法做到兩全其美,

我們就可以根據系統對高性能和高可靠性的要求,來選擇使用哪種寫回策略了, -
想要獲得高性能,就選擇 No 策略;
-
想要得到高可靠性保證,就選擇 Always 策略;
-
允許資料有一點丟失,又希望性能別受太大影響的話,那么就選擇 Everysec 策略,
3.日志的重寫
重寫的作用
AOF 是以檔案的形式在記錄接收到的所有寫命令,「隨著接收的寫命令越來越多,AOF 檔案會越來越大」,這也就意味著,我們一定要小心 AOF 檔案過大帶來的性能問題,主要在于以下三個方面:
- 一是,檔案系統本身對檔案大小有限制,無法保存過大的檔案;
- 二是,如果檔案太大,之后再往里面追加命令記錄的話,效率也會變低;
- 三是,如果發生宕機,AOF 中記錄的命令要一個個被重新執行,用于故障恢復,如果日志檔案太大,整個恢復程序就會非常緩慢,這就會影響到
Redis 的正常使用,
AOF 重寫機制就是在重寫時,Redis 根據資料庫的現狀創建一個新的 AOF 檔案,也就是說,「讀取資料庫中的所有鍵值對,然后對每一個鍵值對用一條命令記錄它的寫入」,重寫機制具有“多變一”功能,所謂的“多變一”,也就是說,舊日志檔案中的多條命令,在重寫后的新日志中變成了一條命令,

重寫的程序
AOF 日志由主執行緒寫回不同,重寫程序是由「后臺子行程 bgrewriteaof 來完成的,這也是為了避免阻塞主執行緒」,導致資料庫性能下降,
我把重寫的程序總結為“「一個拷貝,兩處日志」”,
“一個拷貝”就是指,每次執行重寫時,主執行緒 fork 出后臺的 bgrewriteaof 子行程,此時,fork 會把主執行緒的記憶體拷貝一份給 bgrewriteaof 子行程,這里面就包含了資料庫的最新資料,然后,bgrewriteaof 子行程就可以在不影響主執行緒的情況下,逐一把拷貝的資料寫成操作,記入重寫日志,
第一處日志,指的是因為主執行緒未阻塞,仍然可以處理新來的操作,Redis 會把這個操作寫到它的緩沖區,這樣一來,即使宕機了,這個 AOF 日志的操作仍然是齊全的,可以用于恢復,
第二處日志,就是指新的 AOF 重寫日志,這個操作也會被寫到重寫日志的緩沖區,這樣,重寫日志也不會丟失最新的操作,等到拷貝資料的所有操作記錄重寫完成后,重寫日志記錄的這些最新操作也會寫入新的 AOF 檔案,以保證資料庫最新狀態的記錄,
此時,我們就可以用新的 AOF 檔案替代舊檔案了,

總結來說,每次 AOF 重寫時,Redis 會先執行一個記憶體拷貝,用于重寫;然后,使用兩個日志保證在重寫程序中,新寫入的資料不會丟失,而且,「因為 Redis 采用子行程進行日志重寫,所以,這個程序并不會阻塞主執行緒」,
正因為記錄的是操作命令,而不是實際的資料,所以,用 AOF 方法進行故障恢復的時候,需要逐一把操作日志都執行一遍,如果操作日志非常多,Redis 就會恢復得很緩慢,影響到正常使用,這當然不是理想的結果,那么,還有沒有既可以保證可靠性,還能在宕機時實作快速恢復的其他方法呢?
4.RDB
對 Redis 來說,它實作類似照片記錄效果的方式,把某一時刻的狀態以檔案的形式寫到磁盤上,也就是快照(RDB 檔案),這樣一來,即使宕機,快照檔案也不會丟失,資料的可靠性也就得到了保證,
和 AOF 相比,RDB 記錄的是某一時刻的資料,并不是操作,所以,在做資料恢復時,我們可以直接把 RDB 檔案讀入記憶體,很快地完成恢復,
5.快照的原理
Redis 提供了兩個命令來生成 RDB 檔案,分別是 save 和 bgsave,
- 「save」:在主執行緒中執行,會導致阻塞;
- 「bgsave」:創建一個子行程,專門用于寫入 RDB 檔案,避免了主執行緒的阻塞,這也是 Redis RDB 檔案生成的默認配置,
我們可以通過 bgsave 命令來執行全量快照,這既提供了資料的可靠性保證,也避免了對 Redis 的性能影響,
在執行快照的同時,Redis 就會借助作業系統提供的寫時復制技術(Copy-On-Write, COW),正常處理寫操作,bgsave 子行程是由主執行緒 fork 生成的,可以共享主執行緒的所有記憶體資料,bgsave 子行程運行后,開始讀取主執行緒的記憶體資料,并把它們寫入 RDB 檔案,
如果主執行緒對這些資料也都是讀操作(例如圖中的鍵值對 A),那么,主執行緒和 bgsave 子行程相互不影響,但是,如果主執行緒要修改一塊資料(例如圖中的鍵值對 C),那么,這塊資料就會被復制一份,生成該資料的副本(鍵值對 C’),然后,主執行緒在這個資料副本上進行修改,同時,bgsave 子行程可以繼續把原來的資料(鍵值對 C)寫入 RDB 檔案,

這樣既保證了快照的完整性,也允許主執行緒同時對資料進行修改,避免了對正常業務的影響,
來源:
6.混合 AOF/RDB
雖然 bgsave 執行時不阻塞主執行緒,但是,如果頻繁地執行全量快照,也會帶來兩方面的開銷,
一方面,頻繁將全量資料寫入磁盤,會給磁盤帶來很大壓力,多個快照競爭有限的磁盤帶寬,前一個快斬訓沒有做完,后一個又開始做了,容易造成惡性回圈(所以,在 Redis 中如果有一個 bgsave 在運行,就不會再啟動第二個 bgsave 子行程),
另一方面,bgsave 子行程需要通過 fork 操作從主執行緒創建出來,雖然,子行程在創建后不會再阻塞主執行緒,但是,「fork 這個創建程序本身會阻塞主執行緒」,而且主執行緒的記憶體越大,阻塞時間越長,
Redis 4.0 中提出了一個混合使用 AOF 日志和記憶體快照的方法,簡單來說,「記憶體快照以一定的頻率執行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作」,這樣一來,快照不用很頻繁地執行,這就避免了頻繁 fork 對主執行緒的影響,而且,AOF 日志也只用記錄兩次快照間的操作,也就是說,不需要記錄所有操作了,因此,就不會出現檔案過大的情況了,也可以避免重寫開銷,

總結
最后,關于 AOF 和 RDB 的選擇問題,我想再給你提三點建議:
- 資料不能丟失時,記憶體快照和 AOF 的混合使用是一個很好的選擇;
- 如果允許分鐘級別的資料丟失,可以只使用 RDB;
- 如果只用AOF,優先使用 everysec 的配置選項,因為它在可靠性和性能之間取了一個平衡,
小卷聊開發,一個專注于技術、面試,偶爾發點生活的公眾號,關注我,一起變強!!!
本文來自博客園,作者:SmallRoll(小卷),轉載請注明原文鏈接:https://www.cnblogs.com/smallroll/p/17171861.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545525.html
標籤:Java
