Redis中持久化原理是什么

這篇文章將為大家詳細講解有關redis中持久化原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網綜合服務,包含不限于成都網站制作、做網站、臨漳網絡推廣、小程序開發(fā)、臨漳網絡營銷、臨漳企業(yè)策劃、臨漳品牌公關、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;創(chuàng)新互聯(lián)為所有大學生創(chuàng)業(yè)者提供臨漳建站搭建服務,24小時服務熱線:13518219792,官方網址:muchs.cn

Redis持久化機制:

Redis中持久化原理是什么## 寫在前面

本文從整體上詳細介紹Redis的兩種持久化方式,包含工作原理、持久化流程及實踐策略,以及背后的一些理論知識。上一篇文章僅介紹了RDB持久化,但是Redis持久化是一個整體,單獨介紹不成體系,故重新整理?!鞠嚓P推薦:Redis視頻教程】

Redis是一個內存數(shù)據(jù)庫,所有的數(shù)據(jù)將保存在內存中,這與傳統(tǒng)的MySQL、Oracle、SqlServer等關系型數(shù)據(jù)庫直接把數(shù)據(jù)保存到硬盤相比,Redis的讀寫效率非常高。但是保存在內存中也有一個很大的缺陷,一旦斷電或者宕機,內存數(shù)據(jù)庫中的內容將會全部丟失。為了彌補這一缺陷,Redis提供了把內存數(shù)據(jù)持久化到硬盤文件,以及通過備份文件來恢復數(shù)據(jù)的功能,即Redis持久化機制。

Redis支持兩種方式的持久化:RDB快照和AOF。

RDB持久化

RDB快照用官方的話來說:RDB持久化方案是按照指定時間間隔對你的數(shù)據(jù)集生成的時間點快照(point-to-time snapshot)。它以緊縮的二進制文件保存Redis數(shù)據(jù)庫某一時刻所有數(shù)據(jù)對象的內存快照,可用于Redis的數(shù)據(jù)備份、轉移與恢復。到目前為止,仍是官方的默認支持方案。

RDB工作原理

既然說RDB是Redis中數(shù)據(jù)集的時間點快照,那我們先簡單了解一下Redis內的數(shù)據(jù)對象在內存中是如何存儲與組織的。

默認情況下,Redis中有16個數(shù)據(jù)庫,編號從0-15,每個Redis數(shù)據(jù)庫使用一個redisDb對象來表示,redisDb使用hashtable存儲K-V對象。為方便理解,我以其中一個db為例繪制Redis內部數(shù)據(jù)的存儲結構示意圖。

Redis中持久化原理是什么
時間點快照也就是某一時刻Redis內每個DB中每個數(shù)據(jù)對象的狀態(tài),先假設在這一時刻所有的數(shù)據(jù)對象不再改變,我們就可以按照上圖中的數(shù)據(jù)結構關系,把這些數(shù)據(jù)對象依次讀取出來并寫入到文件中,以此實現(xiàn)Redis的持久化。然后,當Redis重啟時按照規(guī)則讀取這個文件中的內容,再寫入到Redis內存即可恢復至持久化時的狀態(tài)。

當然,這個前提時我們上面的假設成立,否則面對一個時刻變化的數(shù)據(jù)集,我們無從下手。我們知道Redis中客戶端命令處理是單線程模型,如果把持久化作為一個命令處理,那數(shù)據(jù)集肯定時處于靜止狀態(tài)。另外,操作系統(tǒng)提供的fork()函數(shù)創(chuàng)建的子進程可獲得與父進程一致的內存數(shù)據(jù),相當于獲取了內存數(shù)據(jù)副本;fork完成后,父進程該干嘛干嘛,持久化狀態(tài)的工作交給子進程就行了。

很顯然,第一種情況不可取,持久化備份會導致短時間內Redis服務不可用,這對于高HA的系統(tǒng)來講是無法容忍的。所以,第二種方式是RDB持久化的主要實踐方式。由于fork子進程后,父進程數(shù)據(jù)一直在變化,子進程并不與父進程同步,RDB持久化必然無法保證實時性;RDB持久化完成后發(fā)生斷電或宕機,會導致部分數(shù)據(jù)丟失;備份頻率決定了丟失數(shù)據(jù)量的大小,提高備份頻率,意味著fork過程消耗較多的CPU資源,也會導致較大的磁盤I/O。

持久化流程

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

  • rdbSave:是同步執(zhí)行的,方法調用后就會立刻啟動持久化流程。由于Redis是單線程模型,持久化過程中會阻塞,Redis無法對外提供服務;

  • rdbSaveBackground:是后臺(異步)執(zhí)行的,該方法會fork出子進程,真正的持久化過程是在子進程中執(zhí)行的(調用rdbSave),主進程會繼續(xù)提供服務;

RDB持久化的觸發(fā)必然離不開以上兩個方法,觸發(fā)的方式分為手動和自動。手動觸發(fā)容易理解,是指我們通過Redis客戶端人為的對Redis服務端發(fā)起持久化備份指令,然后Redis服務端開始執(zhí)行持久化流程,這里的指令有save和bgsave。自動觸發(fā)是Redis根據(jù)自身運行要求,在滿足預設條件時自動觸發(fā)的持久化流程,自動觸發(fā)的場景有如下幾個(摘自這篇文章):

  • serverCron中save m n配置規(guī)則自動觸發(fā);

  • 從節(jié)點全量復制時,主節(jié)點發(fā)送rdb文件給從節(jié)點完成復制操作,主節(jié)點會出發(fā)bgsave;

  • 執(zhí)行debug reload命令重新加載redis時;

  • 默認情況下(未開啟AOF)執(zhí)行shutdown命令時,自動執(zhí)行bgsave;

結合源碼及參考文章,我整理了RDB持久化流程來幫助大家有個整體的了解,然后再從一些細節(jié)進行說明。
Redis中持久化原理是什么
從上圖可以知道:

  • 自動觸發(fā)的RDB持久化是通過rdbSaveBackground以子進程方式執(zhí)行的持久化策略;

  • 手動觸發(fā)是以客戶端命令方式觸發(fā)的,包含save和bgsave兩個命令,其中save命令是在Redis的命令處理線程以阻塞的方式調用rdbSave方法完成的。

自動觸發(fā)流程是一個完整的鏈路,涵蓋了rdbSaveBackground、rdbSave等,接下來我以serverCron為例分析一下整個流程。

save規(guī)則及檢查

serverCron是Redis內的一個周期性函數(shù),每隔100毫秒執(zhí)行一次,它的其中一項工作就是:根據(jù)配置文件中save規(guī)則來判斷當前需要進行自動持久化流程,如果滿足條件則嘗試開始持久化。了解一下這部分的實現(xiàn)。

redisServer中有幾個與RDB持久化有關的字段,我從代碼中摘出來,中英文對照著看下:

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

/* 對應redis.conf中的save參數(shù) */
struct saveparam {
    time_t seconds;                    /* 統(tǒng)計時間范圍 */   
    int changes;                    /* 數(shù)據(jù)修改次數(shù) */
};

saveparams對應redis.conf下的save規(guī)則,save參數(shù)是Redis觸發(fā)自動備份的觸發(fā)策略,seconds為統(tǒng)計時間(單位:秒), changes為在統(tǒng)計時間內發(fā)生寫入的次數(shù)。save m n的意思是:m秒內有n條寫入就觸發(fā)一次快照,即備份一次。save參數(shù)可以配置多組,滿足在不同條件的備份要求。如果需要關閉RDB的自動備份策略,可以使用save ""。以下為幾種配置的說明:

# 表示900秒(15分鐘)內至少有1個key的值發(fā)生變化,則執(zhí)行
save 900 1
# 表示300秒(5分鐘)內至少有1個key的值發(fā)生變化,則執(zhí)行
save 300 10
# 表示60秒(1分鐘)內至少有10000個key的值發(fā)生變化,則執(zhí)行
save 60 10000
# 該配置將會關閉RDB方式的持久化
save ""

serverCron對RDB save規(guī)則的檢測代碼如下所示:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他邏輯 */
    
    /* 如果用戶請求進行AOF文件重寫時,Redis正在執(zhí)行RDB持久化,Redis會安排在RDB持久化完成后執(zhí)行AOF文件重寫,
     * 如果aof_rewrite_scheduled為true,說明需要執(zhí)行用戶的請求 */
    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    {
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
    } else {
        /* 后臺無 saving/rewrite 子進程才會進行,逐個檢查每個save規(guī)則*/
        for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;
            
            /* 檢查規(guī)則有幾個:滿足修改次數(shù),滿足統(tǒng)計周期,達到重試時間間隔或者上次持久化完成*/
            if (server.dirty >= sp->changes 
                && server.unixtime-server.lastsave > sp->seconds 
                &&(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY || server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...", sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                /* 執(zhí)行bgsave過程 */
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

        /* 省略:Trigger an AOF rewrite if needed. */
    }
    /* 省略其他邏輯 */
}

如果沒有后臺的RDB持久化或AOF重寫進程,serverCron會根據(jù)以上配置及狀態(tài)判斷是否需要執(zhí)行持久化操作,判斷依據(jù)就是看lastsave、dirty是否滿足saveparams數(shù)組中的其中一個條件。如果有一個條件匹配,則調用rdbSaveBackground方法,執(zhí)行異步持久化流程。

rdbSaveBackground

rdbSaveBackground是RDB持久化的輔助性方法,主要工作是fork子進程,然后根據(jù)調用方(父進程或者子進程)不同,有兩種不同的執(zhí)行邏輯。

  • 如果調用方是父進程,則fork出子進程,保存子進程信息后直接返回。

  • 如果調用方是子進程則調用rdbSave執(zhí)行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);
        // 執(zhí)行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是真正執(zhí)行持久化的方法,它在執(zhí)行時存在大量的I/O、計算操作,耗時、CPU占用較大,在Redis的單線程模型中持久化過程會持續(xù)占用線程資源,進而導致Redis無法提供其他服務。為了解決這一問題Redis在rdbSaveBackground中fork出子進程,由子進程完成持久化工作,避免了占用父進程過多的資源。

需要注意的是,如果父進程內存占用過大,fork過程會比較耗時,在這個過程中父進程無法對外提供服務;另外,需要綜合考慮計算機內存使用量,fork子進程后會占用雙倍的內存資源,需要確保內存夠用。通過info stats命令查看latest_fork_usec選項,可以獲取最近一個fork以操作的耗時。

rdbSave

Redis的rdbSave函數(shù)是真正進行RDB持久化的函數(shù),流程、細節(jié)賊多,整體流程可以總結為:創(chuàng)建并打開臨時文件、Redis內存數(shù)據(jù)寫入臨時文件、臨時文件寫入磁盤、臨時文件重命名為正式RDB文件、更新持久化狀態(tài)信息(dirty、lastsave)。其中“Redis內存數(shù)據(jù)寫入臨時文件”最為核心和復雜,寫入過程直接體現(xiàn)了RDB文件的文件格式,本著一圖勝千言的理念,我按照源碼流程繪制了下圖。
Redis中持久化原理是什么
補充說明一下,上圖右下角“遍歷當前數(shù)據(jù)庫的鍵值對并寫入”這個環(huán)節(jié)會根據(jù)不同類型的Redis數(shù)據(jù)類型及底層數(shù)據(jù)結構采用不同的格式寫入到RDB文件中,不再展開了。我覺得大家對整個過程有個直觀的理解就好,這對于我們理解Redis內部的運作機制大有裨益。

AOF持久化

上一節(jié)我們知道RDB是一種時間點(point-to-time)快照,適合數(shù)據(jù)備份及災難恢復,由于工作原理的“先天性缺陷”無法保證實時性持久化,這對于緩存丟失零容忍的系統(tǒng)來說是個硬傷,于是就有了AOF。

AOF工作原理

AOF是Append Only File的縮寫,它是Redis的完全持久化策略,從1.1版本開始支持;這里的file存儲的是引起Redis數(shù)據(jù)修改的命令集合(比如:set/hset/del等),這些集合按照Redis Server的處理順序追加到文件中。當重啟Redis時,Redis就可以從頭讀取AOF中的指令并重放,進而恢復關閉前的數(shù)據(jù)狀態(tài)。

AOF持久化默認是關閉的,修改redis.conf以下信息并重啟,即可開啟AOF持久化功能。

# no-關閉,yes-開啟,默認no
appendonly yes
appendfilename appendonly.aof

AOF本質是為了持久化,持久化對象是Redis內每一個key的狀態(tài),持久化的目的是為了在Reids發(fā)生故障重啟后能夠恢復至重啟前或故障前的狀態(tài)。相比于RDB,AOF采取的策略是按照執(zhí)行順序持久化每一條能夠引起Redis中對象狀態(tài)變更的命令,命令是有序的、有選擇的。把aof文件轉移至任何一臺Redis Server,從頭到尾按序重放這些命令即可恢復如初。舉個例子:

首先執(zhí)行指令set number 0,然后隨機調用incr number、get number 各5次,最后再執(zhí)行一次get number ,我們得到的結果肯定是5。

因為在這個過程中,能夠引起number狀態(tài)變更的只有set/incr類型的指令,并且它們執(zhí)行的先后順序是已知的,無論執(zhí)行多少次get都不會影響number的狀態(tài)。所以,保留所有set/incr命令并持久化至aof文件即可。按照aof的設計原理,aof文件中的內容應該是這樣的(這里是假設,實際為RESP協(xié)議):

set number 0
incr number
incr number
incr number
incr number
incr number

最本質的原理用“命令重放”四個字就可以概括。但是,考慮實際生產環(huán)境的復雜性及操作系統(tǒng)等方面的限制,Redis所要考慮的工作要比這個例子復雜的多:

  • Redis Server啟動后,aof文件一直在追加命令,文件會越來越大。文件越大,Redis重啟后恢復耗時越久;文件太大,轉移工作就越難;不加管理,可能撐爆硬盤。很顯然,需要在合適的時機對文件進行精簡。例子中的5條incr指令很明顯的可以替換為為一條set命令,存在很大的壓縮空間。

  • 眾所周知,文件I/O是操作系統(tǒng)性能的短板,為了提高效率,文件系統(tǒng)設計了一套復雜的緩存機制,Redis操作命令的追加操作只是把數(shù)據(jù)寫入了緩沖區(qū)(aof_buf),從緩沖區(qū)到寫入物理文件在性能與安全之間權衡會有不同的選擇。

  • 文件壓縮即意味著重寫,重寫時即可依據(jù)已有的aof文件做命令整合,也可以先根據(jù)當前Redis內數(shù)據(jù)的狀態(tài)做快照,再把存儲快照過程中的新增的命令做追加。

  • aof備份后的文件是為了恢復數(shù)據(jù),結合aof文件的格式、完整性等因素,Redis也要設計一套完整的方案做支持。

持久化流程

從流程上來看,AOF的工作原理可以概括為幾個步驟:命令追加(append)、文件寫入與同步(fsync)、文件重寫(rewrite)、重啟加載(load),接下來依次了解每個步驟的細節(jié)及背后的設計哲學。
Redis中持久化原理是什么

命令追加

當 AOF 持久化功能處于打開狀態(tài)時,Redis 在執(zhí)行完一個寫命令之后,會以協(xié)議格式(也就是RESP,即 Redis 客戶端和服務器交互的通信協(xié)議 )把被執(zhí)行的寫命令追加到 Redis 服務端維護的 AOF 緩沖區(qū)末尾。對AOF文件只有單線程的追加操作,沒有seek等復雜的操作,即使斷電或宕機也不存在文件損壞風險。另外,使用文本協(xié)議好處多多:

  • 文本協(xié)議有很好的兼容性;

  • 文本協(xié)議就是客戶端的請求命令,不需要二次處理,節(jié)省了存儲及加載時的處理開銷;

  • 文本協(xié)議具有可讀性,方便查看、修改等處理。

AOF緩沖區(qū)類型為Redis自主設計的數(shù)據(jù)結構sds,Redis會根據(jù)命令的類型采用不同的方法(catAppendOnlyGenericCommand、catAppendOnlyExpireAtCommand等)對命令內容進行處理,最后寫入緩沖區(qū)。

需要注意的是:如果命令追加時正在進行AOF重寫,這些命令還會追加到重寫緩沖區(qū)(aof_rewrite_buffer)。

文件寫入與同步

AOF文件的寫入與同步離不開操作系統(tǒng)的支持,開始介紹之前,我們需要補充一下Linux I/O緩沖區(qū)相關知識。硬盤I/O性能較差,文件讀寫速度遠遠比不上CPU的處理速度,如果每次文件寫入都等待數(shù)據(jù)寫入硬盤,會整體拉低操作系統(tǒng)的性能。為了解決這個問題,操作系統(tǒng)提供了延遲寫(delayed write)機制來提高硬盤的I/O性能。
Redis中持久化原理是什么

傳統(tǒng)的UNIX實現(xiàn)在內核中設有緩沖區(qū)高速緩存或頁面高速緩存,大多數(shù)磁盤I/O都通過緩沖進行。 當將數(shù)據(jù)寫入文件時,內核通常先將該數(shù)據(jù)復制到其中一個緩沖區(qū)中,如果該緩沖區(qū)尚未寫滿,則并不將其排入輸出隊列,而是等待其寫滿或者當內核需要重用該緩沖區(qū)以便存放其他磁盤塊數(shù)據(jù)時, 再將該緩沖排入到輸出隊列,然后待其到達隊首時,才進行實際的I/O操作。這種輸出方式就被稱為延遲寫。

延遲寫減少了磁盤讀寫次數(shù),但是卻降低了文件內容的更新速度,使得欲寫到文件中的數(shù)據(jù)在一段時間內并沒有寫到磁盤上。當系統(tǒng)發(fā)生故障時,這種延遲可能造成文件更新內容的丟失。為了保證磁盤上實際文件系統(tǒng)與緩沖區(qū)高速緩存中內容的一致性,UNIX系統(tǒng)提供了sync、fsync和fdatasync三個函數(shù)為強制寫入硬盤提供支持。

Redis每次事件輪訓結束前(beforeSleep)都會調用函數(shù)flushAppendOnlyFile,flushAppendOnlyFile會把AOF緩沖區(qū)(aof_buf)中的數(shù)據(jù)寫入內核緩沖區(qū),并且根據(jù)appendfsync配置來決定采用何種策略把內核緩沖區(qū)中的數(shù)據(jù)寫入磁盤,即調用fsync()。該配置有三個可選項alwaysno、everysec,具體說明如下:

  • always:每次都調用fsync(),是安全性最高、性能最差的一種策略。

  • no:不會調用fsync()。性能最好,安全性最差。

  • everysec:僅在滿足同步條件時調用fsync()。這是官方建議的同步策略,也是默認配置,做到兼顧性能和數(shù)據(jù)安全性,理論上只有在系統(tǒng)突然宕機的情況下丟失1秒的數(shù)據(jù)。

注意:上面介紹的策略受配置項no-appendfsync-on-rewrite的影響,它的作用是告知Redis:AOF文件重寫期間是否禁止調用fsync(),默認是no。

如果appendfsync設置為alwayseverysec,后臺正在進行的BGSAVE或者BGREWRITEAOF消耗過多的磁盤I/O,在某些Linux系統(tǒng)配置下,Redis對fsync()的調用可能阻塞很長時間。然而這個問題還沒有修復,因為即使是在不同的線程中執(zhí)行fsync(),同步寫入操作也會被阻塞。

為了緩解此問題,可以使用該選項,以防止在進行BGSAVEBGREWRITEAOF時在主進程中調用fsync()。

  • 設置為yes意味著,如果子進程正在進行BGSAVEBGREWRITEAOF,AOF的持久化能力就與appendfsync設置為no有著相同的效果。最糟糕的情況下,這可能會導致30秒的緩存數(shù)據(jù)丟失。

  • 如果你的系統(tǒng)有上面描述的延遲問題,就把這個選項設置為yes,否則保持為no。

文件重寫

如前面提到的,Redis長時間運行,命令不斷寫入AOF,文件會越來越大,不加控制可能影響宿主機的安全。

為了解決AOF文件體積問題,Redis引入了AOF文件重寫功能,它會根據(jù)Redis內數(shù)據(jù)對象的最新狀態(tài)生成新的AOF文件,新舊文件對應的數(shù)據(jù)狀態(tài)一致,但是新文件會具有較小的體積。重寫既減少了AOF文件對磁盤空間的占用,又可以提高Redis重啟時數(shù)據(jù)恢復的速度。還是下面這個例子,舊文件中的6條命令等同于新文件中的1條命令,壓縮效果顯而易見。
Redis中持久化原理是什么
我們說,AOF文件太大時會觸發(fā)AOF文件重寫,那到底是多大呢?有哪些情況會觸發(fā)重寫操作呢?
**
與RDB方式一樣,AOF文件重寫既可以手動觸發(fā),也會自動觸發(fā)。手動觸發(fā)直接調用bgrewriteaof命令,如果當時無子進程執(zhí)行會立刻執(zhí)行,否則安排在子進程結束后執(zhí)行。自動觸發(fā)由Redis的周期性方法serverCron檢查在滿足一定條件時觸發(fā)。先了解兩個配置項:

  • auto-aof-rewrite-percentage:代表當前AOF文件大?。╝of_current_size)和上一次重寫后AOF文件大小(aof_base_size)相比,增長的比例。

  • auto-aof-rewrite-min-size:表示運行BGREWRITEAOF時AOF文件占用空間最小值,默認為64MB;

Redis啟動時把aof_base_size初始化為當時aof文件的大小,Redis運行過程中,當AOF文件重寫操作完成時,會對其進行更新;aof_current_sizeserverCron執(zhí)行時AOF文件的實時大小。當滿足以下兩個條件時,AOF文件重寫就會觸發(fā):

增長比例:(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage
文件大?。篴of_current_size > auto-aof-rewrite-min-size

手動觸發(fā)與自動觸發(fā)的代碼如下,同樣在周期性方法serverCron中:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他邏輯 */
    
    /* 如果用戶請求進行AOF文件重寫時,Redis正在執(zhí)行RDB持久化,Redis會安排在RDB持久化完成后執(zhí)行AOF文件重寫,
     * 如果aof_rewrite_scheduled為true,說明需要執(zhí)行用戶的請求 */
    if (!hasActiveChildProcess() &&
        server.aof_rewrite_scheduled)
    {
        rewriteAppendOnlyFileBackground();
    }

    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    {
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
    } else {
        /* 省略rdb持久化條件檢查 */

        /* AOF重寫條件檢查:aof開啟、無子進程運行、增長百分比已設置、當前文件大小超過閾值 */
        if (server.aof_state == AOF_ON &&
            !hasActiveChildProcess() &&
            server.aof_rewrite_perc &&
            server.aof_current_size > server.aof_rewrite_min_size)
        {
            long long base = server.aof_rewrite_base_size ?
                server.aof_rewrite_base_size : 1;
            /* 計算增長百分比 */
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
        }
    }
    /**/
}

AOF文件重寫的流程是什么?聽說Redis支持混合持久化,對AOF文件重寫有什么影響?

從4.0版本開始,Redis在AOF模式中引入了混合持久化方案,即:純AOF方式、RDB+AOF方式,這一策略由配置參數(shù)aof-use-rdb-preamble(使用RDB作為AOF文件的前半段)控制,默認關閉(no),設置為yes可開啟。所以,在AOF重寫過程中文件的寫入會有兩種不同的方式。當aof-use-rdb-preamble的值是:

  • no:按照AOF格式寫入命令,與4.0前版本無差別;

  • yes:先按照RDB格式寫入數(shù)據(jù)狀態(tài),然后把重寫期間AOF緩沖區(qū)的內容以AOF格式寫入,文件前半部分為RDB格式,后半部分為AOF格式。

結合源碼(6.0版本,源碼太多這里不貼出,可參考aof.c)及參考資料,繪制AOF重寫(BGREWRITEAOF)流程圖:
Redis中持久化原理是什么
結合上圖,總結一下AOF文件重寫的流程:

  • rewriteAppendOnlyFileBackground開始執(zhí)行,檢查是否有正在進行的AOF重寫或RDB持久化子進程:如果有,則退出該流程;如果沒有,則繼續(xù)創(chuàng)建接下來父子進程間數(shù)據(jù)傳輸?shù)耐ㄐ殴艿?。?zhí)行fork()操作,成功后父子進程分別執(zhí)行不同的流程。

  • 父進程:

    • 記錄子進程信息(pid)、時間戳等;

    • 繼續(xù)響應其他客戶端請求;

    • 收集AOF重寫期間的命令,追加至aof_rewrite_buffer;

    • 等待并向子進程同步aof_rewrite_buffer的內容;

  • 子進程:

    • 修改當前進程名稱,創(chuàng)建重寫所需的臨時文件,調用rewriteAppendOnlyFile函數(shù);

    • 根據(jù)aof-use-rdb-preamble配置,以RDB或AOF方式寫入前半部分,并同步至硬盤;

    • 從父進程接收增量AOF命令,以AOF方式寫入后半部分,并同步至硬盤;

    • 重命名AOF文件,子進程退出。

數(shù)據(jù)加載

Redis啟動后通過loadDataFromDisk函數(shù)執(zhí)行數(shù)據(jù)加載工作。這里需要注意,雖然持久化方式可以選擇AOF、RDB或者兩者兼用,但是數(shù)據(jù)加載時必須做出選擇,兩種方式各自加載一遍就亂套了。

理論上,AOF持久化比RDB具有更好的實時性,當開啟了AOF持久化方式,Redis在數(shù)據(jù)加載時優(yōu)先考慮AOF方式。而且,Redis 4.0版本后AOF支持了混合持久化,加載AOF文件需要考慮版本兼容性。Redis數(shù)據(jù)加載流程如下圖所示:
Redis中持久化原理是什么
在AOF方式下,開啟混合持久化機制生成的文件是“RDB頭+AOF尾”,未開啟時生成的文件全部為AOF格式??紤]兩種文件格式的兼容性,如果Redis發(fā)現(xiàn)AOF文件為RDB頭,會使用RDB數(shù)據(jù)加載的方法讀取并恢復前半部分;然后再使用AOF方式讀取并恢復后半部分。由于AOF格式存儲的數(shù)據(jù)為RESP協(xié)議命令,Redis采用偽客戶端執(zhí)行命令的方式來恢復數(shù)據(jù)。

如果在AOF命令追加過程中發(fā)生宕機,由于延遲寫的技術特點,AOF的RESP命令可能不完整(被截斷)。遇到這種情況時,Redis會按照配置項aof-load-truncated執(zhí)行不同的處理策略。這個配置是告訴Redis啟動時讀取aof文件,如果發(fā)現(xiàn)文件被截斷(不完整)時該如何處理:

  • yes:則盡可能多的加載數(shù)據(jù),并以日志的方式通知用戶;

  • no:則以系統(tǒng)錯誤的方式崩潰,并禁止啟動,需要用戶修復文件后再重啟。

總結

Redis提供了兩種持久化的選擇:RDB支持以特定的實踐間隔為數(shù)據(jù)集生成時間點快照;AOF把Redis Server收到的每條寫指令持久化到日志中,待Redis重啟時通過重放命令恢復數(shù)據(jù)。日志格式為RESP協(xié)議,對日志文件只做append操作,無損壞風險。并且當AOF文件過大時可以自動重寫壓縮文件。

當然,如果你不需要對數(shù)據(jù)進行持久化,也可以禁用Redis的持久化功能,但是大多數(shù)情況并非如此。實際上,我們時有可能同時使用RDB和AOF兩種方式的,最重要的就是我們要理解兩者的區(qū)別,以便合理使用。

RDB vs AOF

RDB優(yōu)點
  • RDB是一個緊湊壓縮的二進制文件,代表Redis在某一個時間點上的數(shù)據(jù)快照,非常適合用于備份、全量復制等場景。

  • RDB對災難恢復、數(shù)據(jù)遷移非常友好,RDB文件可以轉移至任何需要的地方并重新加載。

  • RDB是Redis數(shù)據(jù)的內存快照,數(shù)據(jù)恢復速度較快,相比于AOF的命令重放有著更高的性能。

RDB缺點
  • RDB方式無法做到實時或秒級持久化。因為持久化過程是通過fork子進程后由子進程完成的,子進程的內存只是在fork操作那一時刻父進程的數(shù)據(jù)快照,而fork操作后父進程持續(xù)對外服務,內部數(shù)據(jù)時刻變更,子進程的數(shù)據(jù)不再更新,兩者始終存在差異,所以無法做到實時性。

  • RDB持久化過程中的fork操作,會導致內存占用加倍,而且父進程數(shù)據(jù)越多,fork過程越長。

  • Redis請求高并發(fā)可能會頻繁命中save規(guī)則,導致fork操作及持久化備份的頻率不可控;

  • RDB文件有文件格式要求,不同版本的Redis會對文件格式進行調整,存在老版本無法兼容新版本的問題。

AOF優(yōu)點
  • AOF持久化有更好的實時性,我們可以選擇三種不同的方式(appendfsync):no、every second、always,every second作為默認的策略具有最好的性能,極端情況下可能會丟失一秒的數(shù)據(jù)。

  • AOF文件只有append操作,無復雜的seek等文件操作,沒有損壞風險。即使最后寫入數(shù)據(jù)被截斷,也很容易使用redis-check-aof工具修復;

  • 當AOF文件變大時,Redis可在后臺自動重寫。重寫過程中舊文件會持續(xù)寫入,重寫完成后新文件將變得更小,并且重寫過程中的增量命令也會append到新文件。

  • AOF文件以已于理解與解析的方式包含了對Redis中數(shù)據(jù)的所有操作命令。即使不小心錯誤的清除了所有數(shù)據(jù),只要沒有對AOF文件重寫,我們就可以通過移除最后一條命令找回所有數(shù)據(jù)。

  • AOF已經支持混合持久化,文件大小可以有效控制,并提高了數(shù)據(jù)加載時的效率。

AOF缺點
  • 對于相同的數(shù)據(jù)集合,AOF文件通常會比RDB文件大;

  • 在特定的fsync策略下,AOF會比RDB略慢。一般來講,fsync_every_second的性能仍然很高,fsync_no的性能與RDB相當。但是在巨大的寫壓力下,RDB更能提供最大的低延時保障。

  • 在AOF上,Redis曾經遇到一些幾乎不可能在RDB上遇到的罕見bug。一些特殊的指令(如BRPOPLPUSH)導致重新加載的數(shù)據(jù)與持久化之前不一致,Redis官方曾經在相同的條件下進行測試,但是無法復現(xiàn)問題。

使用建議

對RDB和AOF兩種持久化方式的工作原理、執(zhí)行流程及優(yōu)缺點了解后,我們來思考下,實際場景中應該怎么權衡利弊,合理的使用兩種持久化方式。如果僅僅是使用Redis作為緩存工具,所有數(shù)據(jù)可以根據(jù)持久化數(shù)據(jù)庫進行重建,則可關閉持久化功能,做好預熱、緩存穿透、擊穿、雪崩之類的防護工作即可。

一般情況下,Redis會承擔更多的工作,如分布式鎖、排行榜、注冊中心等,持久化功能在災難恢復、數(shù)據(jù)遷移方面將發(fā)揮較大的作用。建議遵循幾個原則:

  • 不要把Redis作為數(shù)據(jù)庫,所有數(shù)據(jù)盡可能可由應用服務自動重建。

  • 使用4.0以上版本Redis,使用AOF+RDB混合持久化功能。

  • 合理規(guī)劃Redis最大占用內存,防止AOF重寫或save過程中資源不足。

  • 避免單機部署多實例。

  • 生產環(huán)境多為集群化部署,可在slave開啟持久化能力,讓master更好的對外提供寫服務。

  • 備份文件應自動上傳至異地機房或云存儲,做好災難備份。

關于fork()

通過上面的分析,我們都知道RDB的快照、AOF的重寫都需要fork,這是一個重量級操作,會對Redis造成阻塞。因此為了不影響Redis主進程響應,我們需要盡可能降低阻塞。

  • 降低fork的頻率,比如可以手動來觸發(fā)RDB生成快照、與AOF重寫;

  • 控制Redis最大使用內存,防止fork耗時過長;

  • 使用更高性能的硬件;

  • 合理配置Linux的內存分配策略,避免因為物理內存不足導致fork失敗。

關于“Redis中持久化原理是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

分享題目:Redis中持久化原理是什么
瀏覽地址:http://muchs.cn/article14/gdchde.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供企業(yè)網站制作、軟件開發(fā)、全網營銷推廣響應式網站、網站維護、搜索引擎優(yōu)化

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網站網頁設計