深度剖析Redis持久化機制

Redis將數據存儲在內存中,宕機或重啓都會使內存數據所有丟失, Redis的持久化機制用來保證數據不會由於故障而丟失。Redis提供兩種持久化方式,一種爲內存快照方式,生成rdb文件,rdb是某一時間點內存數據的全量備份,文件內容是存儲結構很是緊湊的二進制序列化形式;另外一種是AOF日誌備份方式,日誌保存的是基於數據的連續增量備份,日誌文件內容是服務端執行的每一條指令的記錄文本。兩種方式各有優略,下面的章節會詳細介紹兩種持久化機制的實現原理和使用技巧。算法

1.內存快照

Redis進行快照數據持久化時,爲了避免阻塞線上業務,要可以響應客戶端請求。快照持久化的工做是將一個時間點內存數據序列化後同步到磁盤rdb文件。備份數據如何在內存中瞬間凝固,再也不改變?文件IO操作怎樣才能不拖累服務端對客戶端的正常響應?這一切都要從Copy On Write提及。數據庫

1.1 快照原理——Copy On Write

Copy On Write簡寫爲COW,又叫寫時複製,是操做系統爲優化使用子進程採起的一種策略。安全

類Unix系統建立進程的主要方式是調用glibc的函數fork,熟悉Linux的人都知道:Linux操做系統的進程都是經過init進程(pid=1)或者其子進程fork(vfork)出來的。bash

fork()會產生一個與父進程徹底相同的子進程,有兩次返回:將子進程的pid返回給父進程,0返回給子進程。若是小於0,說明建立進程失敗!下面是一個C語言的例子:服務器

#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t pid;
    int count = 0;
    pid = fork();
    if (pid < 0)
        printf("error in fork!");
    else if (pid == 0) {
        printf("child process, process id is %d/n", getpid());
        count++;
    } else {
        printf("parent process, process id is %d/n", getpid());
        count++;
    }
    printf("count total: %d/n", count);
    return 0;
}
複製代碼

輸出結果爲:數據結構

parent process, process id is 23049
count total: 1
child process, process id is 23050
count total: 1
複製代碼

當前進程調用fork(),會建立一個跟當前進程徹底相同的子進程(除了pid),因此子進程一樣是會執行fork()以後的代碼。父子進程的count變量都是1,說明父子進程使用了各自獨有的棧區(count變量存放在棧區)。app

咱們先來看一下CPU執行程序的流程。異步

CPU在加載執行程序時,首先按照虛擬地址來尋址,而後經過MMU(內存管理單元)將虛擬地址轉換爲物理地址。由於只有程序的一部分加入到內存中(按頁加載),因此會出現所尋找的地址不在內存中的狀況(CPU產生缺頁異常),若是在內存不足的狀況下,就會經過頁面調度算法來將內存中的頁面置換出來,而後將在外存中的頁面加入到內存中,使程序繼續正常運行。函數

Linux操做系統的每個進程,都會分配有虛擬地址物理地址,虛擬地址和物理地址經過MMU保持映射關係。一個進程是一個主體,它有靈魂有身體,靈魂就是其虛擬地址空間(有相應的數據結構表示),包括:正文段、數據段、堆、棧這四個部分;相應的,內核會爲這四個部分分配各自的物理塊(進程的身體)即:正文段塊、數據段塊、堆塊、棧塊。性能

咱們再來看一下fork進程時寫時複製的過程:

寫時複製原理

fork產生子進程時,操做系統只爲新生成的子進程建立虛擬空間結構,它們複製於父進程的虛擬空間結構,可是不爲這些段分配物理內存,它們共享父進程的物理空間,當父子進程中有更改相應段的行爲發生時,再爲子進程相應的段分配物理空間,這就是寫時複製。

1.2 快照執行流程

Redis在持久化時會調用glibc的函數fork產生一個子進程,快照持久化徹底交給子進程來處理,父進程繼續處理客戶端請求。

rdb快照執行流程

能夠經過在Redis客戶端輸入bgsave命令來觸發快照保存操做,Redis調用bgsaveCommand函數,該函數fork一個子進程,子進程剛剛產生時,它和父進程共享內存裏面的代碼段和數據段。這時將父子進程比喻成一個連體嬰兒很是恰當,這是Linux操做系統的機制,爲了節約內存資源,儘量的將內存資源共享起來。在進程分離的一瞬間,內存的增加幾乎沒有明顯的變化。子進程由於沒有數據的變化,它能感知到的內存數據在進程產生的一瞬間就凝固了,不再會改變。父進程能夠繼續處理客戶端請求,當子進程推出後,父進程調用相關函數處理子進程的善後工做。

2.AOF持久化

AOF日誌存儲的Redis服務器的順序指令序列,只記錄對內存進行修改的指令記錄。有了AOF文件,就能夠經過一個空的Redis實例順序執行全部的指令來恢復Redis當前實例的內存數據結構的狀態,這個過程叫作重放

2.1 AOF日誌文件寫入

AOF日誌以文件的形式存在,寫文件經過操做系統提供的write函數執行,可是write以後的數據只是寫到了內核的一個緩衝區中,而後內核還須要異步的調用fsync函數異步的將數據刷回磁盤。fsync函數是一個阻塞而且緩慢的操做,若是機器忽然宕機,AOF日誌內容可能還沒來的及徹底刷到磁盤,這時候就會丟失數據。Redis經過appendfsync配置控制執行fsync的頻次,具體有以下三種模式:

  • no: 永遠不調用fsync,讓操做系統決定什麼時候同步磁盤,這樣作很不安全,可是Redis的性能最高。
  • always: 每執行一次寫入操做就執行一次fsync,雖然數據安全性高,會致使執行很是緩慢。
  • everysec: 每隔1s執行一次fsync,這個1s的週期是能夠配置的,這是數據安全性和性能之間的折中方案,在保證高性能的同時,儘可能使數據少丟失。推薦在生產環境中使用。

2.2 AOF執行流程

Redis收到客戶端的指令之後,首先進行參數校驗、邏輯處理,若是沒有問題,會判斷是否開啓AOF,若是開啓,則會將每條命令執行完畢後同步寫入aof_buf中,aof_buf是個全局的SDS類型的緩衝區。

AOF執行流程

每一條命令的執行都會調用call函數,注意:Redis服務端是先執行指令再將命令寫入aof_buf。

2.3 日誌瘦身——AOF重寫

Redis服務端在長期運行過程當中,AOF日誌會愈來愈長,若是實例宕機或者重啓,重放整個AOF日誌會很是耗時,致使Redis長時間沒法對外提供服務,因此須要對AOF日誌進行瘦身,即:AOF重寫

咱們考慮一下AOF和RDB文件的加載過程:RDB只須要把相應的數據加載都內存並生成相應的數據結構就能夠了,有些結構如intset、ziplist,保存的時候直接按照字符串保存,加載速度很是快。可是AOF日誌文件的加載須要建立一個僞客戶端,順序執行一遍命令,根據Redis做者作的測試,RDB在10~20秒能加載1GB的文件,AOF的速度是RDB的一半(若是作了AOF重寫會加快)

經過Redis客戶端bgrewriteaof指令對AOF日誌進行瘦身過程以下:

AOF日誌瘦身過程

Redis服務端調用bgrewriteaofCommand命令建立管道,建立管道對做用是AOF重寫過程當中批量接收服務端累積的命令;建立完管道之後,fork進程,子進程調用rewriteAppendOnlyFile執行AOF重寫操做;父進程記錄一些統計指標後繼續進入主循環處理客戶端請求,待子進程結束之後,處理一些善後工做。瘦身工做就是子進程對全部數據庫中的鍵各自生成一條相應的執行命令,最後將重寫開始後父進程繼續執行的命令進行回放,生成一個新的AOF文件。

例如執行了下面的命令:

127.0.0.1:6379> lpush list guo zhao ran
(integer) 3
127.0.0.1:6379> lpop list
"ran"
127.0.0.1:6379> lpop list
"zhao"
127.0.0.1:6379> lpush list zhao
(integer) 2
複製代碼

AOF文件會保存對list操做的4條命令,可是list如今內存中的元素是這樣的:

127.0.0.1:6379> lrange list 0 -1
1) "zhao"
2) "guo"
複製代碼

AOF重寫之後就日誌文件內容直接就變爲了lpush list zhao guo。日誌瘦身既能夠減少文件大小,又能夠提升加載速度。

3.混合持久化

RDB和AOF實現持久化的方式各有優缺點,咱們來簡單總結一下:

RDB保存的是一個時間的快照,若是發生故障,丟失的是最後一次RDB執行時間點到故障發生的時間間隔以內產生的數據。若是Redis數據量很大,QPS很高,執行一次RDB須要的時間會相應增長,發生故障時丟失的數據也會增多。

AOF保存的是一條條的命令,理論上能夠作到發生故障時只丟失一條命令。可是因爲操做系統中執行寫文件操做代價很大,Redis提供了配置參數,能夠對徹底性和性能取折中,設置不一樣的配置策略。可是重放AOF日誌相對於使用RDB來講仍是慢不少。

Redis4.0爲了解決這個問題,帶來了一個新的持久化選項——混合持久化。混合持久化是指進行AOF重寫時子進程將當前時間點的數據快照保存爲RDB文件格式,然後將父進程累積命令保存爲AOF格式,最終生成的格式以下圖所示:

混合持久化文件

將RDB文件內容和增量AOF日誌文件存在一塊兒,這裏的AOF日誌再也不是全量日誌,一般這部分AOF日誌很小。因而在Redis重啓的時候,能夠先加載rdb內容,而後再重放增量AOF日誌,就能夠徹底替代以前的AOF全量文件重放,重啓效率會獲得大幅度提高。

4. Redis持久化相關配置

下面總結一下Redis4.0版持久化相關的配置及其含義。

配置項 可選值 默認值 做用
save save <seconds> <changes> save 900 1
save 300 10
save 60 10000
save "":禁用快照備份,默認關閉
save 900 1:在900秒內有1個key被改動,自動保存到dump.rdb文件中
save 300 10:在300秒內有10個key被改動,自動保存到dump.rdb文件中
save 60 10000:在60秒內有10000個key被改動,自動保存到dump.rdb文件中
以上3中條件任意一種被知足就會觸發保存
stop-writes-on-bgsave-error yes/no yes 開啓該參數後,若是開啓了RDB快照(即配置了save指令),而且最近一次快照執行失敗,則Redis將中止接收寫相關的請求
rdbcompression yes/no yes 執行rdb的時候是否將string類型的數據壓縮
rdbchecksum yes/no yes 是否開啓rdb文件內容的校驗
dbfilename 文件名稱 dump.rdb rdb文件名稱
dir 文件路徑 ./ RDB和AOF文件存放路徑
appendonly yes/no no 是否開啓AOF功能
appendfilename 文件名稱 appendonly.aof AOF文件名稱
appendfsync always/everysec/no everysec fsync執行頻次,上邊有說到
no-appendfsync-on-rewrite yes/no no 開啓該參數後,若是後臺正在執行一次rdb快照或者aof重寫,則主進程再也不進行fsync操做,即便將appendsync配置成always或者everysec
auto-aof-rewrite-percentage 百分比 100 和auto-aof-rewrite-min-size配和使用,下面會講解
auto-aof-rewrite-min-size 文件大小 64M 當AOF文件大於64M時,而且AOF文件當前大小比基準大小增加了100%時會觸發一次AOF重寫。
aof-load-truncated yes/no yes AOF以追加日誌的方式生成,當服務端發生故障時會有命令不完整的狀況。開啓該參數後,在這種狀況下,AOF會截斷尾部不完整的命令繼續加載,而且在日誌中給出提示。
aof-use-rdb-preamble yes/no yes 是否開啓混合持久化
aof-rewrite-incremental-fsync yes/no yes 開啓該參數後,AOF重寫時每產生32MB數據執行一次fsync

5.總結

Redis是內存數據庫,機器故障或重啓以後,內存數據所有丟失,因此須要持久化來保證數據安全。Redis提供了快照RDB和AOF日誌同步兩種方式進行數據持久化,快照RDB實現原理是Copy On Write,優勢是機器加載速度快,缺點是執行緩慢,QPS高的狀況下會丟失大量數據;AOF則是將命令一條條的有序存放到日誌文件中,優勢是儘量少的丟失數據,缺點是日誌文件重放緩慢,日誌文件會很大,能夠經過重寫AOF日誌來實現,另外提供了這種的配置方案異步執行fsync操做。生產環境中推薦使用混合持久化,這種方式綜合了RDB和AOF兩種方式的優勢。文章最後總結了一下Redis持久化配置項。本文是筆者學習Redis持久化的總結,但願能對讀者有所幫助。

相關文章
相關標籤/搜索