Redis的持久化設計

Redis 持久化設計

持久化的功能:Redis是內存數據庫,數據都是存儲在內存中的,為了避免進程退出導致數據的永久丟失,要定期將Redis中的數據以某種形式從內存保存到硬盤,當下次Reids重啟時,利用持久化文件實現數據恢復。

RDB:將當前數據保存到硬盤

AOF:將每次執行的寫命令保存到硬盤(類似MySQL的binlog)

1. RDB持久化

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

  1. 觸發條件

    • 手動觸發 save 命令和bgsave命令都可以生成RDB文件, save命令會阻塞Redis服務進程,知道RDB文件創建完畢,bgsave命令則是創建一個子進程,由子進程來負責創建RDB文件,父進程繼續處理請求,bgsave命令執行過程中,只有fork子進程時會阻塞服務器,而對於save命令,整個過程都會阻塞服務器,因此save已基本被廢棄,線上環境要杜絕save的使用;後文中也將只介紹bgsave命令。此外,在自動觸發RDB持久化時,Redis也會選擇bgsave而不是save來進行持久化

    SAVE 執行期間,AOF 寫入可以在後台線程進行,BGREWRITEAOF 可以在子進程進行,所以這三種操作可以同時進行 ,為了避免性能問題,BGSAVE 和 BGREWRITEAOF 不能同時執行

  2. 自動觸發

save m n

在配置文件中通過 save m n 命令,指定當前m秒內發生n次變化時,觸發bgsave。

​ 其中save 900 1的含義是:當時間到900秒時,如果redis數據發生了至少1次變化,則執行bgsave;save 300 10和save 60 10000同理。當三個save條件滿足任意一個時,都會引起bgsave的調用.

Redis的save m n,是通過serverCron函數、dirty計數器、和lastsave時間戳來實現的-

  • serverCron函數,是Redis服務器的周期性操作函數,默認每隔100ms執行一次,該函數對服務器的狀態進行維護,其中一項工作就是檢測save m n 配置是否滿足條件,如果滿足就執行bgsave.
  • dirty計數器 記錄服務器進行了多少起操作,修改,不是客戶端執行了多少修改數據的命令
  • lastsave時間戳也是Reids服務器維持的一個狀態,記錄上一次成功執行bgsave的時間

save m n的原理如下:每隔100ms,執行serverCron函數;在serverCron函數中,遍歷save m n配置的保存條件,只要有一個條件滿足,就進行bgsave。對於每一個save m n條件,只有下面兩條同時滿足時才算滿足:

  • 當前時間-lastsave > m

  • dirty >= n

在主從複製場景下,如果從節點執行全量複製操作,則主節點會執行bgsave命令,並將rdb文件發送給從節點。

在執行shutdown命令時,自動執行rdb持久化

1.2 RDB文件

設置存儲路徑

- 配置文件:dir配置指定目錄,dbfilename指定文件名。默認是Redis根目錄下的dump.rdb文件
- 動態設置: 

config set dir {newdir} /// config set dbfilename {newFileName}

RDB文件 是經過壓縮的二進制文件,默認採用LZF算法對RDB文件進行壓縮,雖然壓縮耗時,但是可以大大減小文件體積,默認是開啟的,可以通過命令關閉:

config set rdbcompression no

RDB文件的壓縮並不是針對整個文件進行的,而是對數據庫中的字符串進行的,且只有在字符串達到一定長度(20字節)時才會進行

格式:

字段說明:

  1. REDIS常量,保存‘REDIS’5個字符

  2. db_version RDB文件的版本號

  3. SELECTDB 表示一個完整的數據庫(0號數據庫),同理SELECTDB 3 pairs表示完整的3號數據庫;只有當數據庫中有鍵值對時,RDB文件中才會有該數據庫的信息(上圖所示的Redis中只有0號和3號數據庫有鍵值對);如果Redis中所有的數據庫都沒有鍵值對,則這一部分直接省略。其中:SELECTDB是一個常量,代表後面跟着的是數據庫號碼;0和3是數據庫號碼;

  4. KEY-VALUE-PAIRS: pairs則存儲了具體的鍵值對信息,包括key、value值,及其數據類型、內部編碼、過期時間、壓縮信息等等

  1. EOF 標志著數據庫內容的結尾(不是文件的結尾),值為 rdb.h/EDIS_RDB_OPCODE_EOF (255)

  2. CHECK-SUM RDB 文件所有內容的校驗和,一個 uint_64t 類型值, REDIS 在寫入 RDB 文件時將校驗和保存在 RDB 文件的末尾,當讀取時,根據它的值對內容進行校驗

。如果這個域的值為 0 ,那麼表示 Redis 關閉了校驗和功能。

1.3 啟動時加載

​ RDB文件的載入工作是在服務器啟動時自動執行的,並沒有專門的命令。但是由於AOF的優先級更高,因此當AOF開啟時,Redis會優先載入AOF文件來恢複數據;只有當AOF關閉時,才會在Redis服務器啟動時檢測RDB文件,並自動載入。服務器載入RDB文件期間處於阻塞狀態,直到載入完成為止

2. AOF持久化

AOF(Append Only File) 則以協議文本的方式,將所有對數據庫進行過寫入的命令(及其參數)記錄到 AOF
文件,以此達到記錄數據庫狀態的目的

2.1 開啟AOF

Redis服務器默認開啟RDB,關閉AOF;要開啟AOF,需要在配置文件中配置:

appendonly yes

2.2 執行流程

2.2.1 命令寫入緩衝區

//緩衝區的定義 是一個SDS, 可以兼容C語言的字符串
struct redisServer {
    // AOF緩衝區, 在進入事件loop之前寫入
    sds aof_buf;
};
  1. 命令傳播: Redis將執行完的命令、命令的參數、命令的參數個數等信息發送到 AOF 程序中

  2. 緩存追加: AOF程序根據接收到的命令命令數據,將命令轉換為網絡通訊協議的格式,然後將協議內容追加到服務器的 AOF 緩存中。

    • 將命令以文本協議格式保存在緩存中
    • 為什麼使用文本協議格式?兼容性,避免二次開銷,可讀性
    • 為什麼寫入緩存?這樣不會受制於磁盤的IO性能,避免每次有寫命令都直接寫入硬盤,導致硬盤IO成為Redis負載的瓶頸
  3. 文件寫入和保存:AOF 緩存中的內容被寫入到 AOF 文件末尾,如果設定的 AOF 保存
    條件被滿足的話,fsync 函數或者 fdatasync 函數會被調用,將寫入的內容真正地保存到磁盤中。

    為了提高文件寫入效率,在現代操作系統中,當用戶調用write函數將數據寫入文件時,操作系統通常會將數據暫存到一個內存緩衝區里,當緩衝區被填滿或超過了指定時限后,才真正將緩衝區的數據寫入到硬盤裡。這樣的操作雖然提高了效率,但也帶來了安全問題:如果計算機停機,內存緩衝區中的數據會丟失;因此系統同時提供了fsync、fdatasync等同步函數,可以強制操作系統立刻將緩衝區中的數據寫入到硬盤裡,從而確保數據的安全性。

    AOF保存模式:

    • AOF_FSYNC_ALWAYS: 命令寫入aof-buf后立即調用系統的fsync操作同步到AOF文件。因為 SAVE 是由 Redis 主進程執行的,所以在 SAVE 執行期間,主進程會被阻塞,不能接受命令請求。這種情況下,每次有寫命令都要同步到AOF文件,硬盤IO成為性能瓶頸,Redis只能支持大約幾百TPS寫入,嚴重降低了Redis的性能;即便是使用固態硬盤(SSD),每秒大約也只能處理幾萬個命令,而且會大大降低SSD的壽命。
    • AOF_FSYNC_NO: 命令寫入aof_buf后調用系統write操作,不對AOF文件做fsync同步;同步由操作系統負責,通常同步周期為30秒。這種情況下,文件同步的時間不可控,且緩衝區中堆積的數據會很多,數據安全性無法保證。
    • AOF_FSYNC_EVERYSEC: 每一秒鐘保存一次,命令寫入aof_buf后調用系統write操作, write完成后線程返回, fsync同步文件操作由專門線程每秒調用一次

2.2.2. 文件重寫

隨着命令不斷寫入AOF,文件會越來越大,為了解決這個問題,Redis引入AOF重寫機制壓縮文件體積,AOF文件重寫是把Redis進程內的數據轉化為寫命令同步到新AOF文件的過程。

重寫后的AOF文件為什麼可以變小?

  1. 進程內已經超時的數據不再寫入文件
  2. 舊的AOF文件含有無效命令 ,如有些數據被重複設值(set mykey v1, set mykey v2)、有些數據被刪除了(sadd myset v1, del myset)等等, 新的AOF文件只保留最終的數據寫入命令
  3. 多條寫入命令可以合併為一個,如:lpush list a、lpush list b可以轉化為:lpush list a b。為了防止單條命令過大造成客戶端緩衝區溢出,對於list、set、hash等類型操作,以64個元素為邊界拆分為多條

AOF重寫可以手動觸發也可以自動觸發:

  • 手動觸發: 直接調用bgrewriteaof命令
  • 自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數確定自動觸發時機。
    • auto-aof-rewrite-min-size:表示運行AOF重寫時文件最小體積,默認為64MB
    • auto-aof-rewrite-percentage:代表當前AOF文件空間(aof_current_size)和上一次重寫后AOF文件空間(aof_base_size)的比值

流程說明:

1)執行AOF重寫請求。

如果當前進程正在執行AOF重寫,請求不執行。

如果當前進程正在執行bgsave操作,重寫命令延遲到bgsave完成之後再執行。

2)父進程執行fork創建子進程,開銷等同於bgsave過程。

3.1)主進程fork操作完成后,繼續響應其它命令。

  所有修改命令依然寫入AOF文件緩衝區並根據appendfsync策略同步到磁盤,保證原有AOF機制正確性。

3.2)由於fork操作運用寫時複製技術,子進程只能共享fork操作時的內存數據

  由於父進程依然響應命令,Redis使用“AOF”重寫緩衝區保存這部分新數據,防止新的AOF文件生成期間丟失這部分數據。

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

  每次批量寫入硬盤數據量由配置aof-rewrite-incremental-fsync控制,默認為32MB,防止單次刷盤數據過多造成硬盤阻塞。

5.1)新AOF文件寫入完成后,子進程發送信號給父進程,父進程調用一個信號處理函數,並執行以前操作更新統計信息。

5.2)父進程把AOF重寫緩衝區的數據寫入到新的AOF文件。這時新 AOF 文件所保存的數據庫狀態將和服務器當前的數據庫狀態一致。

5.3)對新的AOF文件進行改名,原子地(atomic)覆蓋現有的AOF文件,完成新舊文件的替換。

在整個 AOF 後台重寫過程中,只有信號處理函數執行時會對服務器進程(父進程)造成阻塞,其他時候,AOF 後台重寫都不會阻塞父進程,這將 AOF 重寫對服務器性能造成的影響降到了最低

參考《Redis-設計與實現:AOF-持久化》

2.2.3 重啟加載

流程說明:

1)AOF持久化開啟且存在AOF文件時,優先加載AOF文件。

2)AOF關閉或者AOF文件不存在時,加載RDB文件。

3)加載AOF/RDB文件成功后,Redis啟動成功。

4)AOF/RDB文件存在錯誤時,Redis啟動失敗並打印錯誤信息。

數據還原的詳細步驟:

  1. 創建一個不帶網絡連接的偽客戶端(fake client): 因為 Redis 的命令只能在客戶端上下文中執行,而載入 AOF 文件時所使用的命令直接來源於 AOF 文件而不是網絡連接,所以服務器使用了一個沒有網絡連接的偽客戶端來執行 AOF 文件保存的寫命令,偽客戶端執行命令的效果和帶網絡連接的客戶端執行命令的效果完全一樣。
  2. 從AOF文件中分析並讀取出一條寫命令,使用偽客戶端執行被讀出的寫命令,重複此操作,直到AOF文件中的所有寫命令都被處理完畢為止。

2.2.4 文件校驗

加載損壞的AOF文件會拒絕啟動,並打印錯誤信息。

注意:對於錯誤格式的AOF文件,先進性備份,然後採用redis-check-aof --fix命令進行修復,修復后使用diff -u對比數據差異,找到丟失的數據,有些可以進行人工補全。

AOF文件可能存在結尾不完整的情況,比如機器突然掉電導致AOF尾部文件命令寫入不全。

Redis為我們提高了aof-load-truncated配置來兼容這種情況,默認開啟

3. 了解MySQL中的binlog

mysql binlog應用場景與原理深度剖析

參考博文與書籍:

  1. 《redis設計與實現》
  2. Redis持久化
  3. [徐劉根-Redis實戰和核心原理詳解(8)使用快照RDB和AOF將Redis數據持久化到硬盤中](https://blog.csdn.net/xlgen157387/article/details/61925524

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心