Redis持久化:RDB和AOF
Redis 資料存儲在記憶體中,如果不想辦法将資料儲存到硬碟上,一旦Redis重新開機(退出/故障),記憶體的資料将會全部丢失。我們肯定不想 Redis 裡的資料由于某些故障全部丢失(導緻所有請求都走 MySQL),即便發生了故障也希望可以将Redis原有的資料恢複過來,這就是持久化的作用。
Redis 提供了兩種不同的持久化方法來将資料存儲到硬碟裡邊:
RDB(Redis Database) ,将某一時刻的所有資料儲存到一個 RDB 檔案中。 AOF(append-only-file) ,當Redis伺服器執行寫指令的時候,将執行的寫指令儲存到 AOF 檔案中。
RDB記憶體快照,讓當機快速恢複
1.什麼是RDB記憶體快照?
在 Redis 執行“寫”指令的過程中,記憶體資料一直會變化,所謂記憶體快照,指的就是 Redis 記憶體中資料在某一時刻的狀态資料,好比時間定格在某一時刻。當我們拍照時,通過照片就能把某一時刻的瞬間畫面完全記錄下來。Redis 跟這個類似,就是把某一刻的資料以檔案的形式拍下來,寫到磁盤上,這個快照檔案叫做 RDB 檔案,RDB 就是 Redis Database 的縮寫。
2.生成RDB的政策
Redis 并不會在每次執行“寫”指令的時候都觸發 RDB 寫磁盤,隻需要在執行記憶體快照的時候寫磁盤,這樣既保證了唯快不破,還實作了持久化,當機快速恢複。
我們知道 Redis 的單線程模型決定了我們要盡可能的避免會阻塞主線程的操作,是以就需要盡可能的避免 RDB 檔案生成阻塞主線程。為此Redis提供了兩個指令用于生成 RDB 檔案:
SAVE:會阻塞 Redis 伺服器程序,伺服器不能接收任何請求,直到 RDB 檔案建立完畢為止。 BGSAVE:fork 出一個子程序,由子程序來負責建立 RDB 檔案,伺服器程序可以繼續接收請求。
除了手動調用 SAVE 或者 BGSAVE 指令生成 RDB 檔案之外,我們可以使用配置的方式來定期執行:在預設的配置下,如果以下的條件被觸發,就會執行 BGSAVE 指令。
save 900 1 #在900秒(15分鐘)之後,至少有1個key發生變化,
save 300 10 #在300秒(5分鐘)之後,至少有10個key發生變化
save 60 10000 #在60秒(1分鐘)之後,至少有10000個key發生變化
複制代碼
3.RDB實作原理
在RDB執行期間為了保證快照的資料一緻性,隻能處理讀操作,不能修改正在執行快照的資料,這種場景,Redis 是允許的。那 Redis 是如何實作一邊處理寫請求,同時生成RDB檔案的呢?
Redis 使用作業系統的多程序寫時複制技術 COW(Copy On Write) 來實作快照的持久化。
Redis 在持久化是會調用 glibc 的函數(linux系統中最底層的api)fork産生一個子程序,快照持久化完全交給子程序來處理,父程序繼續處理用戶端請求。子程序剛剛産生時,它和父程序共享記憶體裡面的代碼段和資料段,這時可以将父子程序想象成一個連體嬰兒,共享身體。這是Linux作業系統的機制,為了節約記憶體資源,是以盡可能讓它們共享起來,在程序分離的一瞬間,記憶體的增長幾乎沒有明顯的變化。
BGSAVE 子程序可以共享主線程的所有記憶體資料,讀取主線程的資料并寫入到 RDB 檔案。當主線程執行寫指令修改資料的時候,這個資料就會複制一份副本,BGSAVE 子程序讀取這個副本資料寫到 RDB 檔案。
在執行 SAVE 或 BGSAVE 指令建立一個新的 RDB 檔案時,程式會對資料庫中的鍵進行檢查,已過期的鍵不會被儲存到新建立的RDB 檔案中。這既保證了快照的完整性,也允許主線程同時對資料進行修改,避免了對正常業務的影響。
4.RDB的優缺點
優點
RDB 檔案是一個很簡潔的單檔案,采用 二進制 + 資料壓縮 的方式寫磁盤,檔案體積小,資料恢複速度快。 RDB 的性能很好,需要進行持久化時,主程序會 fork 一個子程序出來,然後把持久化的工作交給子程序,自己不會有相關的I/O操作。
缺點
RDB 容易造成資料的丢失。假設每5分鐘儲存一次快照,如果 Redis 因為某些原因不能正常工作,那麼從上次産生快照到 Redis 出現問題這段時間的資料就會丢失了。 RDB 使用 fork() 産生子程序進行資料的持久化,會阻塞主線程,如果資料比較大的話可能就會花費點時間,造成 Redis 停止服務幾毫秒。如果資料量很大且CPU性能不是很好的時候,停止服務的時間甚至會到1秒。
另外,過于頻繁的執行全量資料快照,有兩個嚴重的性能開銷:
頻繁生成 RDB 檔案寫入磁盤,磁盤壓力過大。可能會出現上一個 RDB 還未完成,下一個又開始生成,陷入死循環。 fork 出 BGSAVE 子程序這個動作本身會阻塞主線程,主線程的記憶體越大,阻塞時間越長。
AOF寫後日志,避免當機資料丢失
1.什麼是AOF寫後日志?
AOF(Append Only File)寫後日志,AOF 持久化就是将修改資料庫狀态的指令儲存到 AOF 檔案中,被寫入的指令都是以 Redis 的指令請求協定格式儲存的,Redis 的指令請求協定是純文字格式。
假設 AOF 日志記錄了 Redis 執行個體建立以來所有的修改指令序列,那麼就可以通過一個空的 Redis 執行個體順序執行所有的指令,也就是“重放”,來恢複Redis目前執行個體的記憶體資料結構的狀态。
寫後日志和寫前日志的對比
寫前日志(WAL,Write Ahead Log) :在實際寫資料之前,将修改的資料寫到日志檔案中,故障恢複得以保證。比如 MySQL Innodb 存儲引擎中的 redo log(重做日志)便是記錄修改的資料日志,在實際修改資料前先記錄修改日志再執行修改資料。
寫後日志:先執行“寫”指令請求,将資料寫入記憶體,再記錄日志。
日志格式
當 Redis 接收到 “set key value” 指令将資料寫入到記憶體之後,會按照如下格式寫入 AOF 檔案:
*3:表示目前指令分為三個部分,每部分都是 “$ + 數字” 開頭,緊跟後面是該部分具體的指令、鍵、值 數字:表示這部分的指令、鍵、值占用的位元組大小。比如 “$3” 表示這部分包含三個位元組,也就是 set 指令。
寫後日志的好處
寫後日志避免了額外的檢查開銷,不需要對執行的指令進行文法檢查。如果使用寫前日志的話,就需要先檢查文法是否有誤,否則日志記錄了錯誤的指令,在使用日志恢複的時候就會報錯。另外,寫後記錄日志,避免了阻塞目前“寫”指令的執行。
2.寫回政策
使用 AOF 也不是萬無一失的,假如 Redis 剛執行完指令,還沒記錄日志就當機了,就有可能丢失這個指令的相關資料;還有, AOF 避免了目前指令的阻塞,但是可能會給下一個指令帶來阻塞的風險。 AOF 日志是主線程執行的,将日志寫入磁盤過程中,如果磁盤壓力過大就會導緻磁盤寫操作很慢,導緻後續的“寫”指令阻塞。
發現了沒,這兩個問題與磁盤寫回有關,如果能合理控制“寫”指令執行完後 AOF 日志寫回磁盤的時機,問題就可以迎刃而解。
為了提高檔案的寫入效率,當使用者調用 write 函數,将一些資料寫入到檔案時候,作業系統通常會将寫入資料暫時儲存在一個記憶體緩沖區裡,等到緩沖區的空間被填滿或者超過了制定的限制之後,才真正将緩沖區中的資料寫入到磁盤裡面。
這種做法雖然提高了效率,但也為寫入資料帶來了安全問題,因為如果計算機發生停機,那麼儲存在記憶體緩沖區裡的寫入資料将會丢失。為此系統提供了 fsync 和 fdatasync 兩個同步函數,它們可以強制讓作業系統立即将緩沖區中的資料寫入到硬碟裡,進而確定寫入資料的安全性。
與之相對應 Redis 提供了 AOF 配置項 appendfsync 寫回政策來控制 AOF 持久化功能的效率和安全性。
appendfsync always # 同步寫回,寫指令執行完畢立即将 aof_buf 緩沖區中的内容寫到 AOF 檔案。
appendfsync everysec # 每秒寫回,寫指令執行完畢,把日志寫到 aof_buf 緩沖區,每隔一秒同步到磁盤,該政策為AOF的預設政策。
appendfsync no # 作業系統控制,寫指令執行完畢,把日志寫到 aof_buf 緩沖區,由作業系統決定何時寫回磁盤。
複制代碼
3.AOF重寫機制
由于 AOF 記錄的是一個個指令的内容,這就會導緻儲存的檔案太大,另外,故障恢複的時候需要執行每一個指令,如果日志檔案太大,整個恢複過程就會非常慢。為此,Reids 設計了 AOF 重寫機制,提供了 bgrewriteaof 指令用于對 AOF 檔案進行瘦身。
其原理就是開辟一個子程序對記憶體進行周遊轉換成一系列 Redis 的操作指令,序列化到一個新的 AOF 日志檔案中,序列化完畢後再将操作期間發生的增量 AOF 日志追加到這個新的 AOF 日志檔案中,追加完畢後立即替換舊的 AOF 日志檔案。瘦身工作就完成了。
重寫機制有“多變一”的功能,将舊日志中的多條指令,在重寫後就變成了一條指令。如下所示:三條 lpush 指令,經過 AOF 重寫後生成一條,對于多次修改的場景,縮減效果明顯。
重寫過程
和 AOF 日志由主線程寫回不同,重寫過程實際是由背景子程序 bgrewriteof 完成的,這也是為了避免阻塞主線程,導緻性能下降。
總的來說,一共出現兩個日志,日志和Redis 資料拷貝。大緻流程如下圖所示:
在上圖中,Redis 會将重寫過程中接收到的“寫”指令操作同時記錄到舊的 AOF 緩沖區和新的 AOF 重寫緩沖區,這樣重寫日志也儲存了最新的操作,等到拷貝資料的所有操作記錄重寫完成後,重寫緩沖區記錄的最新操作也會寫到新的 AOF 檔案中。
每次 AOF 重寫時,Redis 會先執行一次記憶體拷貝,用于周遊資料生成重寫記錄。防止 AOF 重寫過程失敗,導緻原 AOF 檔案被污染,無法做恢複使用。
使用兩個日志可以保證在重寫過程中,新寫入的資料不會丢失,并且保持資料的一緻性。
4.AOF 的優點和缺點
優點
AOF比RDB可靠。可以靈活制定不同的fsync政策。 AOF日志檔案是一個純追加的檔案。就算是遇到突然停電的情況,也不會出現日志的定位或者損壞問題。 當AOF檔案過大時,Redis會自動在背景進行重寫。 AOF以指令格式存儲于檔案中,在資料恢複時,AOF檔案比RDB檔案更容易讓開發人員看懂,并加以修改。
缺點
在相同的資料集下,AOF檔案的大小一般會比RDB檔案大。 在某些fsync政策下,AOF的速度會比RDB慢。通常fsync設定為每秒一次就能獲得比較高的性能,而在禁止fsync的情況下速度可以達到RDB的水準。
混合日志模型
重新開機 Redis 時,我們很少使用 RDB 來恢複記憶體狀态,因為可能丢失大量資料。通常采用 AOF 日志重放,但是重放 AOF 日志性能相對 RDB 來說要慢很多,在Redis執行個體很大的情況下,啟動需要花費很長時間。
Redis 4.0 為了解決這個問題,提供了一個新的持久化選項--混合持久化,将 RDB 檔案的内容和增量 AOF 日志檔案存放到一起,這裡的 AOF 日志不再是全量的日志,而是自持久化開始到持久化結束的這段時間發生的增量 AOF 日志,通常這部分日志很小。
在 Redis 重新開機的時候,先加載 RDB 的内容,然後再重放增量 AOF 日志,這樣的操作既保證了 Redis 重新開機速度,又降低資料丢失風險。
總結
Redis 提供 RDB 快照持久化方案,記錄某一時刻資料狀态 Redis 通過寫時複制技術設計了BGSAVE,避免執行快照期間對讀寫指令的影響。 Redis 提供了 AOF 寫後日志持久化方案,記錄每一條操作指令。 Redis 通過 AOF 重寫方案,避免 AOF檔案過大。 Redis 提供了混合持久化的方案,RDB + AOF 實作持久化保證資料可靠性,同時支援故障後的資料快速恢複。