Redis專題:持久化方式之RDB

公衆號搜索「碼路印記」

寫在前面

Redis是一個內存數據庫,也就是說全部的數據將保存在內存中,這與傳統的MySQL、Oracle、SqlServer等關係型數據庫直接把數據保存到硬盤相比,Redis的讀寫效率很是高。可是保存在內存中也有一個很大的缺陷,一旦斷電或者宕機,內存數據庫中的內容將會所有丟失。爲了彌補這一缺陷,Redis提供了把內存數據持久化到硬盤文件,以及經過備份文件來恢復數據的功能。html

Redis支持兩種方式的持久化:RDB快照文件和AOF。本文先介紹RDB快照方式的工做原理、優缺點等方面進行闡述,寫完AOF方式後再對比二者的優缺點,結合前輩總結給出生產實踐,但願可以對你理解Redis的持久化機制帶來幫助。經過本文你將瞭解到如下內容:~~
image.png
RDB快照用官方的話來講:RDB持久化方案是按照指定時間間隔對你的數據集生成的時間點快照。它是Redis數據庫中數據的內存快照,它是一個二進制文件(默認名稱爲:dump.rdb,可修改),存儲了文件生成時Redis數據庫中全部的數據內容。可用於Redis的數據備份、轉移與恢復。redis

配置參數

RDB快照的觸發方式及運行行爲受配置參數的影響,打開配置文件redis.conf查看「SNAPSHOTTING」章節,瞭解RDB快照的參數及做用。對於各個參數的含義進行了翻譯,英語好的同窗能夠直接看英文。算法

  • save <seconds> <changes>
################################ SNAPSHOTTING  ################################
#
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   In the example below the behavior will be to save:
#   after 900 sec (15 min) if at least 1 key changed
#   after 300 sec (5 min) if at least 10 keys changed
#   after 60 sec if at least 10000 keys changed
#
#   Note: you can disable saving completely by commenting out all "save" lines.
#
#   It is also possible to remove all the previously configured save
#   points by adding a save directive with a single empty string argument
#   like in the following example:
#
#   save ""

save 900 1
save 300 10
save 60 10000

save參數是Redis觸發自動備份的觸發策略,seconds爲統計時間(單位:秒), changes爲在統計時間內發生寫入的次數。save m n的意思是:m秒內有n條寫入就觸發一次快照,即備份一次。save參數能夠配置多組,知足在不一樣條件的備份要求。若是須要關閉RDB的自動備份策略,可使用save ""。如下爲幾種配置的說明:shell

save 900 1:表示900秒(15分鐘)內至少有1個key的值發生變化,則執行
save 300 10:表示300秒(5分鐘)內至少有1個key的值發生變化,則執行
save 60 10000:表示60秒(1分鐘)內至少有10000個key的值發生變化,則執行
save "": 該配置將會關閉RDB方式的持久化
  • dbfilename:快照文件的名稱,默認爲dump.rdb。
  • dir:快照文件保存目錄,默認與當前配置文件在同一目錄。
  • stop-writes-on-bgsave-error:默認值爲yes。當啓用了RDB且最後一次後臺保存數據失敗,Redis是否中止接收數據。這會讓用戶意識到數據沒有正確持久化到磁盤上,不然沒有人會注意到災難(disaster)發生了。若是Redis重啓了,那麼又能夠從新開始接收數據了。
# By default Redis will stop accepting writes if RDB snapshots are enabled
# (at least one save point) and the latest background save failed.
# This will make the user aware (in a hard way) that data is not persisting
# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.
#
# If the background saving process will start working again Redis will
# automatically allow writes again.
#
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
stop-writes-on-bgsave-error yes
  • rdbcompression:默認值yes。備份數據時是否使用LZF算法壓縮字符串對象。默認是開啓的,這樣能夠節約存儲空間,可是在生成備份文件時消耗部分CPU。
# Compress string objects using LZF when dump .rdb databases?
# By default compression is enabled as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes
  • rdbchecksum:保存和加載rdb文件時是否使用CRC64校驗,默認開啓。啓用此參數可使rdb文件更加安全,提升穩定性,可是會有必定的性能(大約10%)損失。若是rdb文件建立時未使用校驗和,那麼校驗和將被設置爲0,以此告知Redis跳過校驗。
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

持久化流程

在Redis內完成RDB持久化的方法有rdbSave和rdbSaveBackground兩個函數方法(源碼文件rdb.c中),先簡單說下二者差異:數據庫

  • rdbSave:是同步執行的,方法調用後就會馬上啓動持久化流程。因爲Redis是單線程模型,持久化過程當中會阻塞,Redis沒法對外提供服務;
  • rdbSaveBackground:是後臺(異步)執行的,該方法會fork出子進程,真正的持久化過程是在子進程中執行的,主進程會繼續提供服務;

RDB持久化的觸發必然離不開以上兩個方法,觸發的方式分爲手動和自動。手動觸發容易理解,是指咱們經過Redis客戶端人爲的對Redis服務端發起持久化備份指令,而後Redis服務端開始執行持久化流程,這裏的指令有save和bgsave。自動觸發是Redis根據自身運行要求,在知足預設條件時自動觸發的持久化流程,自動觸發的場景有以下幾個(摘自這篇文章):數組

  • serverCron中save m n配置規則自動觸發;
  • 從節點全量複製時,主節點發送rdb文件給從節點完成複製操做,主節點會出發bgsave;
  • 執行debug reload命令從新加載redis時;
  • 默認狀況下(未開啓AOF)執行shutdown命令時,自動執行bgsave;

結合源碼及參考文章,我整理了RDB持久化流程來幫助你們有個總體的瞭解,而後再從一些細節進行說明。從下圖能夠知道,自動觸發流程是一個完整的鏈路,涵蓋了rdbSaveBackground、rdbSave等,接下來我以serverCron爲例分析一下整個流程。
image.png安全

serverCron

serverCron是Redis內的一個週期性函數,每隔100毫秒執行一次,它的其中一項工做就是:根據配置文件中save規則來判斷當前須要進行自動持久化流程,若是知足條件則嘗試開始持久化。簡單瞭解一下這部分的運行原理。服務器

第一次遇到這個函數,經過代碼看下這個函數的代碼註釋。咱們能夠發現它會完成過時key處理、軟件監控、更新一些統計數據、觸發RDB持久化或AOF重寫、客戶端超時處理等,本節咱們只關注RDB持久化部分。數據結構

/* This is our timer interrupt, called server.hz times per second.
 * Here is where we do a number of things that need to be done asynchronously.
 * For instance:
 *
 * - Active expired keys collection (it is also performed in a lazy way on
 *   lookup).
 * - Software watchdog.
 * - Update some statistic.
 * - Incremental rehashing of the DBs hash tables.
 * - Triggering BGSAVE / AOF rewrite, and handling of terminated children.
 * - Clients timeout of different kinds.
 * - Replication reconnection.
 * - Many more...
 *
 * Everything directly called here will be called server.hz times per second,
 * so in order to throttle execution of things we want to do less frequently
 * a macro is used: run_with_period(milliseconds) { .... }
 */
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {}

在redisServer中有幾個與RDB持久化有關的字段(以下代碼)。saveparams爲配置文件中的save配置,lastsave爲上次持久化的時間戳,dirty爲上次持久化後發生修改的次數。app

struct redisServer {
    /* 省略其餘字段 */ 
    /* RDB persistence */
    long long dirty;                /* Changes to DB from the last save,上次持久化後修改key的次數 */
    struct saveparam *saveparams;   /* Save points array for RDB,對應配置文件多個save參數 */
    int saveparamslen;              /* Number of saving points,save參數的數量 */
    time_t lastsave;                /* Unix time of last successful save 上次持久化時間*/
    /* 省略其餘字段 */
}

/* 對應redis.conf中的save參數 */
struct saveparam {
    time_t seconds;                    /* 統計時間範圍 */   
    int changes;                    /* 數據修改次數 */
};

這部分的源碼比較簡單,感興趣的同窗能夠下載查看,爲了節省篇幅我就不貼代碼了。若是沒有後臺的RDB持久化或AOF重寫進程,serverCron會根據以上配置及狀態判斷是否須要執行持久化操做,判斷依據就是看lastsave、dirty是否知足saveparams數組中的其中一個條件。若是有一個條件匹配,則調用rdbSaveBackground方法,執行異步持久化流程。

rdbSaveBackground

rdbSaveBackground是RDB持久化的輔助性方法,根據調用方(父進程或者子進程)不一樣,有兩種不一樣的執行邏輯。若是調用方是父進程,則fork出子進程,保存子進程信息後直接返回。若是調用方是子進程則調用rdbSave執行RDB持久化邏輯,持久化完成後退出子進程。

int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
    pid_t childpid;

    if (hasActiveChildProcess()) return C_ERR;

    server.dirty_before_bgsave = server.dirty;
    server.lastbgsave_try = time(NULL);

    // fork子進程
    if ((childpid = redisFork(CHILD_TYPE_RDB)) == 0) {
        int retval;

        /* Child */
        redisSetProcTitle("redis-rdb-bgsave");
        redisSetCpuAffinity(server.bgsave_cpulist);
        // 執行rdb持久化
        retval = rdbSave(filename,rsi);
        if (retval == C_OK) {
            sendChildCOWInfo(CHILD_TYPE_RDB, 1, "RDB");
        }
        // 持久化完成後,退出子進程
        exitFromChild((retval == C_OK) ? 0 : 1);
    } else {
        /* Parent 父進程:記錄fork子進程的時間等信息*/
        if (childpid == -1) {
            server.lastbgsave_status = C_ERR;
            serverLog(LL_WARNING,"Can't save in background: fork: %s",
                strerror(errno));
            return C_ERR;
        }
        serverLog(LL_NOTICE,"Background saving started by pid %ld",(long) childpid);
        server.rdb_save_time_start = time(NULL);
        server.rdb_child_type = RDB_CHILD_TYPE_DISK;
        return C_OK;
    }
    return C_OK; /* unreached */
}

rdbSave是真正執行持久化的方法,它在執行時存在大量的I/O、計算操做,耗時、CPU佔用較大,在Redis的單線程模型中持久化過程會持續佔用線程資源,進而致使Redis沒法提供其餘服務。爲了解決這一問題Redis在rdbSaveBackground中fork出子進程,由子進程完成持久化工做,避免了佔用父進程過多的資源。

須要注意的是,若是父進程內存佔用過大,fork過程會比較耗時,在這個過程當中父進程沒法對外提供服務;另外,須要綜合考慮計算機內存使用量,fork子進程後會佔用雙倍的內存資源,須要確保內存夠用。經過info stats命令查看latest_fork_usec選項,能夠獲取最近一個fork以操做的耗時。

rdbSave

Redis的rdbSave函數是真正進行RDB持久化的函數,流程、細節賊多,總體流程能夠總結爲:建立並打開臨時文件、Redis內存數據寫入臨時文件、臨時文件寫入磁盤、臨時文件重命名爲正式RDB文件、更新持久化狀態信息(dirty、lastsave)。其中「Redis內存數據寫入臨時文件」最爲核心和複雜,寫入過程直接體現了RDB文件的文件格式,本着一圖勝千言的理念,我按照源碼流程繪製了下圖。
image.png
補充說明一下,上圖右下角「遍歷當前數據庫的鍵值對並寫入」這個環節會根據不一樣類型的Redis數據類型及底層數據結構採用不一樣的格式寫入到RDB文件中,再也不展開了。我以爲你們對整個過程有個直觀的理解就好,這對於咱們理解Redis內部的運做機制大有裨益。

數據恢復

數據恢復是自動執行的,咱們將備份文件 (例如:dump.rdb) 移動到Redis備份文件目錄並啓動服務便可,Redis就會自動加載文件數據至內存。Redis 服務器在載入RDB文件期間,會一直處於阻塞狀態,直到載入工做完成爲止。

這裏備份文件名稱及目錄須要於redis.conf中的配置信息保持一致。

RDB優缺點

瞭解了RDB的工做原理後,對於RDB的優缺點就比較容易總結了。先來看下RDB的優勢:

  • RDB是一個緊湊壓縮的二進制文件,表明Redis在某一個時間點上的數據快照,很是適合用於備份、全量複製等場景。
  • RDB對災難恢復、數據遷移很是友好,RDB文件能夠轉移至任何須要的地方並從新加載。
  • RDB是Redis數據的內存快照,數據恢復速度較快,相比於AOF的命令重放有着更高的性能。

事物老是有兩面性的,RDB優勢明顯,一樣也存在缺點:

  • RDB方式沒法作到實時或秒級持久化。由於持久化過程是經過fork子進程後由子進程完成的,子進程的內存只是在fork操做那一時刻父進程的數據快照。而fork操做是一個耗時操做,沒法作到實時性。
  • RDB持久化過程當中的fork操做,會致使內存佔用加倍,並且父進程數據越多,fork過程越長。
  • RDB文件有文件格式要求,不一樣版本的Redis會對文件格式進行調整,存在老版本沒法兼容新版本的問題。

參考文獻

image.png[
](https://juejin.im/post/5d8587...

相關文章
相關標籤/搜索