公衆號搜索「碼路印記」
Redis是一個內存數據庫,也就是說全部的數據將保存在內存中,這與傳統的MySQL、Oracle、SqlServer等關係型數據庫直接把數據保存到硬盤相比,Redis的讀寫效率很是高。可是保存在內存中也有一個很大的缺陷,一旦斷電或者宕機,內存數據庫中的內容將會所有丟失。爲了彌補這一缺陷,Redis提供了把內存數據持久化到硬盤文件,以及經過備份文件來恢復數據的功能。html
Redis支持兩種方式的持久化:RDB快照文件和AOF。本文先介紹RDB快照方式的工做原理、優缺點等方面進行闡述,寫完AOF方式後再對比二者的優缺點,結合前輩總結給出生產實踐,但願可以對你理解Redis的持久化機制帶來幫助。經過本文你將瞭解到如下內容:~~
RDB快照用官方的話來講:RDB持久化方案是按照指定時間間隔對你的數據集生成的時間點快照。它是Redis數據庫中數據的內存快照,它是一個二進制文件(默認名稱爲:dump.rdb,可修改),存儲了文件生成時Redis數據庫中全部的數據內容。可用於Redis的數據備份、轉移與恢復。redis
RDB快照的觸發方式及運行行爲受配置參數的影響,打開配置文件redis.conf
查看「SNAPSHOTTING」章節,瞭解RDB快照的參數及做用。對於各個參數的含義進行了翻譯,英語好的同窗能夠直接看英文。算法
################################ 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方式的持久化
# 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
# 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
# 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中),先簡單說下二者差異:數據庫
RDB持久化的觸發必然離不開以上兩個方法,觸發的方式分爲手動和自動。手動觸發容易理解,是指咱們經過Redis客戶端人爲的對Redis服務端發起持久化備份指令,而後Redis服務端開始執行持久化流程,這裏的指令有save和bgsave。自動觸發是Redis根據自身運行要求,在知足預設條件時自動觸發的持久化流程,自動觸發的場景有以下幾個(摘自這篇文章):數組
save m n
配置規則自動觸發;debug reload
命令從新加載redis時;結合源碼及參考文章,我整理了RDB持久化流程來幫助你們有個總體的瞭解,而後再從一些細節進行說明。從下圖能夠知道,自動觸發流程是一個完整的鏈路,涵蓋了rdbSaveBackground、rdbSave等,接下來我以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是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以操做的耗時。
Redis的rdbSave函數是真正進行RDB持久化的函數,流程、細節賊多,總體流程能夠總結爲:建立並打開臨時文件、Redis內存數據寫入臨時文件、臨時文件寫入磁盤、臨時文件重命名爲正式RDB文件、更新持久化狀態信息(dirty、lastsave)。其中「Redis內存數據寫入臨時文件」最爲核心和複雜,寫入過程直接體現了RDB文件的文件格式,本着一圖勝千言的理念,我按照源碼流程繪製了下圖。
補充說明一下,上圖右下角「遍歷當前數據庫的鍵值對並寫入」這個環節會根據不一樣類型的Redis數據類型及底層數據結構採用不一樣的格式寫入到RDB文件中,再也不展開了。我以爲你們對整個過程有個直觀的理解就好,這對於咱們理解Redis內部的運做機制大有裨益。
數據恢復是自動執行的,咱們將備份文件 (例如:dump.rdb) 移動到Redis備份文件目錄並啓動服務便可,Redis就會自動加載文件數據至內存。Redis 服務器在載入RDB文件期間,會一直處於阻塞狀態,直到載入工做完成爲止。
這裏備份文件名稱及目錄須要於redis.conf中的配置信息保持一致。
瞭解了RDB的工做原理後,對於RDB的優缺點就比較容易總結了。先來看下RDB的優勢:
事物老是有兩面性的,RDB優勢明顯,一樣也存在缺點: