首頁 > 資料庫 > Redis > Redis深入學習之詳解持久化原理

Redis深入學習之詳解持久化原理

青灯夜游
發布: 2022-03-10 10:54:51
轉載
2242 人瀏覽過

這篇文章帶大家深入學習下Redis,詳細解析持久化,介紹其運作方式、持久化流程等,希望對大家有幫助!

本文將從以下幾個面向介紹Redis持久化機制:

寫在前面
##本文從整體詳細介紹Redis的兩種持久化方式,包含工作原理、持久化流程及實務策略,以及背後的一些理論知識。上一篇文章僅介紹了RDB持久化,但Redis持久化是一個整體,單獨介紹不成體系,故重新整理。 【相關推薦:Redis影片教學

Redis是記憶體資料庫,所有的資料會保存在記憶體中,這與傳統的MySQL、Oracle、SqlServer等關係型資料庫直接把資料保存到硬碟相比,Redis的讀寫效率非常高。但是保存在記憶體中也有一個很大的缺陷,一旦斷電或宕機,記憶體資料庫中的內容將會全部遺失。為了彌補這個缺陷,Redis提供了把記憶體資料持久化到硬碟文件,以及透過備份文件來恢復資料的功能,即Redis持久化機制。

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

RDB持久化

RDB快照用官方的話來說:RDB持久化方案是按照指定時間間隔對你的資料集產生的時間點快照(point-to-time snapshot) 。它以緊縮的二進位檔案保存Redis資料庫某一時刻所有資料物件的記憶體快照,可用於Redis的資料備份、轉移與復原。到目前為止,仍是官方的預設支援方案。

RDB工作原理

既然說RDB是Redis中資料集的時間點快照,那我們先簡單了解一下Redis內的資料物件在記憶體中是如何儲存與組織的。

預設情況下,Redis中有16個資料庫,編號從0-15,每個Redis資料庫使用一個redisDb物件來表示,redisDb使用hashtable存儲K-V對象。為方便理解,我以其中一個db為例繪製Redis內部資料的儲存結構示意圖。

Redis深入學習之詳解持久化原理
時間點快照也就是某一刻Redis內每個DB中每個資料物件的狀態,先假設在這一刻所有的資料物件不再改變,我們就可以依照上圖中的資料結構關係,把這些資料物件依序讀取出來並寫入到檔案中,以此實現Redis的持久化。然後,當Redis重新啟動時依照規則讀取這個檔案中的內容,再寫入到Redis記憶體即可恢復至持久化時的狀態。

當然,這個前提時我們上面的假設成立,否則面對一個時刻變化的資料集,我們無從下手。我們知道Redis中客戶端命令處理是單線程模型,如果把持久化當作一個命令處理,那麼資料集肯定時處於靜止狀態。另外,作業系統提供的fork()函數所建立的子行程可取得與父行程一致的記憶體數據,相當於取得了記憶體資料副本;fork完成後,父行程該幹嘛,持久化狀態的工作交給子進程就行了。

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

持久化流程

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

  • rdbSave:是同步執行的,方法呼叫後會立刻啟動持久化流程。由於Redis是單線程模型,持久化過程中會阻塞,Redis無法對外提供服務;
  • rdbSaveBackground:是後台(異步)執行的,該方法會fork出子進程,真正的持久化過程是在子程序中執行的(呼叫rdbSave),主程序會繼續提供服務;

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

    ##serverCron中
  • save m n配置規則自動觸發;
  • 從節點全量複製時,主節點發送rdb檔案給從節點完成複製操作,主節點會出發bgsave;
  • 執行
  • debug reload指令重新載入redis時;
  • 預設(未開啟AOF)執行shutdown指令時,自動執行bgsave;
結合原始碼及參考文章,我整理了RDB持久化流程來幫助大家有個整體的了解,然後再從一些細節來說明。


Redis深入學習之詳解持久化原理從上圖可以知道:

    自動觸發的RDB持久化是透過rdbSaveBackground以子程序方式執行的持久化策略;
  • 手動觸發是以客戶端命令方式觸發的,包含save和bgsave兩個命令,其中save命令是在Redis的命令處理線程以阻塞的方式呼叫
  • rdbSave方法完成的。
自動觸發流程是一個完整的連結,涵蓋了rdbSaveBackground、rdbSave等,接下來我以serverCron為例分析一下整個流程。

save規則及檢查

serverCron是Redis內的週期性函數,每隔100毫秒執行一次,它的其中一項工作就是:根據設定檔中save規則來判斷當前需要進行自動持久化流程,如果滿足條件則嘗試開始持久化。了解一下這部分的實作。

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

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;                    /* 数据修改次数 */
};
登入後複製

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

# 表示900秒(15分钟)内至少有1个key的值发生变化,则执行
save 900 1
# 表示300秒(5分钟)内至少有1个key的值发生变化,则执行
save 300 10
# 表示60秒(1分钟)内至少有10000个key的值发生变化,则执行
save 60 10000
# 该配置将会关闭RDB方式的持久化
save ""
登入後複製

serverCron對RDB save規則的偵測程式碼如下所示:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他逻辑 */
    
    /* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
     * 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (hasActiveChildProcess() || ldbPendingChildren())
    {
        run_with_period(1000) receiveChildInfo();
        checkChildrenDone();
    } else {
        /* 后台无 saving/rewrite 子进程才会进行,逐个检查每个save规则*/
        for (j = 0; j = 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);
                /* 执行bgsave过程 */
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

        /* 省略:Trigger an AOF rewrite if needed. */
    }
    /* 省略其他逻辑 */
}
登入後複製
如果沒有背景的RDB持久化或AOF重寫入進程,serverCron會根據上述配置及狀態判斷是否需要執行持久化操作,判斷依據就是看lastsave、dirty是否滿足saveparams數組中的其中一個條件。如果有一個條件匹配,則呼叫rdbSaveBackground方法,執行非同步持久化流程。

rdbSaveBackground

rdbSaveBackground是RDB持久化的輔助性方法,主要工作是fork子進程,然後根據呼叫方(父進程或子進程)不同,有兩種不同的執行邏輯。

    如果呼叫方是父進程,則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文件的文件格式,本着一图胜千言的理念,我按照源码流程绘制了下图。
Redis深入學習之詳解持久化原理
补充说明一下,上图右下角“遍历当前数据库的键值对并写入”这个环节会根据不同类型的Redis数据类型及底层数据结构采用不同的格式写入到RDB文件中,不再展开了。我觉得大家对整个过程有个直观的理解就好,这对于我们理解Redis内部的运作机制大有裨益。

AOF持久化

上一节我们知道RDB是一种时间点(point-to-time)快照,适合数据备份及灾难恢复,由于工作原理的“先天性缺陷”无法保证实时性持久化,这对于缓存丢失零容忍的系统来说是个硬伤,于是就有了AOF。

AOF工作原理

AOF是Append Only File的缩写,它是Redis的完全持久化策略,从1.1版本开始支持;这里的file存储的是引起Redis数据修改的命令集合(比如:set/hset/del等),这些集合按照Redis Server的处理顺序追加到文件中。当重启Redis时,Redis就可以从头读取AOF中的指令并重放,进而恢复关闭前的数据状态。

AOF持久化默认是关闭的,修改redis.conf以下信息并重启,即可开启AOF持久化功能。

# no-关闭,yes-开启,默认no
appendonly yes
appendfilename appendonly.aof
登入後複製

AOF本质是为了持久化,持久化对象是Redis内每一个key的状态,持久化的目的是为了在Reids发生故障重启后能够恢复至重启前或故障前的状态。相比于RDB,AOF采取的策略是按照执行顺序持久化每一条能够引起Redis中对象状态变更的命令,命令是有序的、有选择的。把aof文件转移至任何一台Redis Server,从头到尾按序重放这些命令即可恢复如初。举个例子:

首先执行指令set number 0,然后随机调用incr numberget number 各5次,最后再执行一次get number ,我们得到的结果肯定是5。

因为在这个过程中,能够引起number状态变更的只有set/incr类型的指令,并且它们执行的先后顺序是已知的,无论执行多少次get都不会影响number的状态。所以,保留所有set/incr命令并持久化至aof文件即可。按照aof的设计原理,aof文件中的内容应该是这样的(这里是假设,实际为RESP协议):

set number 0
incr number
incr number
incr number
incr number
incr number
登入後複製

最本质的原理用“命令重放”四个字就可以概括。但是,考虑实际生产环境的复杂性及操作系统等方面的限制,Redis所要考虑的工作要比这个例子复杂的多:

  • Redis Server启动后,aof文件一直在追加命令,文件会越来越大。文件越大,Redis重启后恢复耗时越久;文件太大,转移工作就越难;不加管理,可能撑爆硬盘。很显然,需要在合适的时机对文件进行精简。例子中的5条incr指令很明显的可以替换为为一条set命令,存在很大的压缩空间。
  • 众所周知,文件I/O是操作系统性能的短板,为了提高效率,文件系统设计了一套复杂的缓存机制,Redis操作命令的追加操作只是把数据写入了缓冲区(aof_buf),从缓冲区到写入物理文件在性能与安全之间权衡会有不同的选择。
  • 文件压缩即意味着重写,重写时即可依据已有的aof文件做命令整合,也可以先根据当前Redis内数据的状态做快照,再把存储快照过程中的新增的命令做追加。
  • aof备份后的文件是为了恢复数据,结合aof文件的格式、完整性等因素,Redis也要设计一套完整的方案做支持。

持久化流程

从流程上来看,AOF的工作原理可以概括为几个步骤:命令追加(append)、文件写入与同步(fsync)、文件重写(rewrite)、重启加载(load),接下来依次了解每个步骤的细节及背后的设计哲学。
Redis深入學習之詳解持久化原理

指令追加

當AOF 持久化功能處於開啟狀態時,Redis 在執行完一個寫入指令之後,會以協定格式(也就是RESP,即Redis 用戶端和伺服器互動的通訊協定)把被執行的寫入指令追加到Redis 服務端維護的AOF 緩衝區末端。對AOF檔案只有單一執行緒的追加操作,沒有seek等複雜的操作,即使斷電或當機也不存在檔案損壞風險。另外,使用文字協定好處多多:

  • 文字協定有很好的兼容性;
  • 文字協定就是客戶端的請求指令,不需要二次處理,節省了儲存及載入時的處理開銷;
  • 文字協定具有可讀性,方便檢視、修改等處理。

AOF緩衝區類型為Redis自主設計的資料結構sds,Redis會根據指令的類型採用不同的方法(catAppendOnlyGenericCommand catAppendOnlyExpireAtCommand等)對指令內容進行處理,最後寫入緩衝區。

要注意的是:如果指令追加時正在進行AOF重寫,這些指令也會追加到重寫緩衝區(aof_rewrite_buffer)。

檔案寫入與同步

AOF檔的寫入與同步離不開作業系統的支持,在開始介紹之前,我們需要補充Linux I/O緩衝區相關知識。硬碟I/O效能較差,檔案讀寫速度遠比不上CPU的處理速度,若每次檔案寫入等待資料寫入硬碟,整體拉低作業系統的效能。為了解決這個問題,作業系統提供了延遲寫入(delayed write)機制來提高硬碟的I/O效能。
Redis深入學習之詳解持久化原理

傳統的UNIX實作在核心中設有緩衝區高速緩存或頁面高速緩存,大多數磁碟I/O都透過緩衝進行。當資料寫入檔案時,核心通常會先將該資料複製到其中一個緩衝區中,如果該緩衝區尚未寫滿,則不會將其排入輸出佇列,而是等待其寫滿或當核心需要重複使用該緩衝區以便存放其他磁碟區塊資料時, 再將該緩衝排入到輸出佇列,然後待到達隊首時,才進行實際的I/O操作。這種輸出方式就稱為延遲寫入。

延遲寫入減少了磁碟讀寫次數,但是卻降低了檔案內容的更新速度,使得欲寫到檔案中的資料在一段時間內並沒有寫到磁碟上。當系統發生故障時,這種延遲可能造成文件更新內容的遺失。為了確保磁碟上實際檔案系統與緩衝區快取中內容的一致性,UNIX系統提供了sync、fsync和fdatasync三個函數為強制寫入硬碟提供支援。

Redis每次事件輪訓結束前(beforeSleep)都會呼叫函數flushAppendOnlyFileflushAppendOnlyFile#會把AOF緩衝區(aof_buf )中的資料寫入核心緩衝區,並且根據appendfsync配置來決定採用何種策略把核心緩衝區中的資料寫入磁碟,即呼叫fsync()。此配置有三個可選項alwaysnoeverysec#,說明如下:

  • always:每次都會呼叫fsync(),是安全性最高、效能最差的策略。
  • no:不會呼叫fsync()。性能最好,安全性最差。
  • everysec:僅在滿足同步條件時呼叫fsync()。這是官方建議的同步策略,也是預設配置,做到兼顧效能和資料安全性,理論上只有在系統突然宕機的情況下遺失1秒的資料。

注意:上面介紹的策略受組態項目no-appendfsync-on-rewrite的影響,它的作用是告知Redis:AOF檔重寫期間是否禁止呼叫fsync(),預設是no。

如果appendfsync設定為alwayseverysec,背景正在進行的BGSAVE BGREWRITEAOF消耗過多的磁碟I/O,在某些Linux系統配置下,Redis對fsync()的呼叫可能會阻塞很久。然而這個問題還沒有修復,因為即使是在不同的執行緒中執行fsync(),同步寫入操作也會被阻塞。

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

  • 设置为yes意味着,如果子进程正在进行BGSAVEBGREWRITEAOF,AOF的持久化能力就与appendfsync设置为no有着相同的效果。最糟糕的情况下,这可能会导致30秒的缓存数据丢失。
  • 如果你的系统有上面描述的延迟问题,就把这个选项设置为yes,否则保持为no

文件重写

如前面提到的,Redis长时间运行,命令不断写入AOF,文件会越来越大,不加控制可能影响宿主机的安全。

为了解决AOF文件体积问题,Redis引入了AOF文件重写功能,它会根据Redis内数据对象的最新状态生成新的AOF文件,新旧文件对应的数据状态一致,但是新文件会具有较小的体积。重写既减少了AOF文件对磁盘空间的占用,又可以提高Redis重启时数据恢复的速度。还是下面这个例子,旧文件中的6条命令等同于新文件中的1条命令,压缩效果显而易见。
Redis深入學習之詳解持久化原理
我们说,AOF文件太大时会触发AOF文件重写,那到底是多大呢?有哪些情况会触发重写操作呢?
**
与RDB方式一样,AOF文件重写既可以手动触发,也会自动触发。手动触发直接调用bgrewriteaof命令,如果当时无子进程执行会立刻执行,否则安排在子进程结束后执行。自动触发由Redis的周期性方法serverCron检查在满足一定条件时触发。先了解两个配置项:

  • auto-aof-rewrite-percentage:代表当前AOF文件大小(aof_current_size)和上一次重写后AOF文件大小(aof_base_size)相比,增长的比例。
  • auto-aof-rewrite-min-size:表示运行BGREWRITEAOF时AOF文件占用空间最小值,默认为64MB;

Redis启动时把aof_base_size初始化为当时aof文件的大小,Redis运行过程中,当AOF文件重写操作完成时,会对其进行更新;aof_current_sizeserverCron执行时AOF文件的实时大小。当满足以下两个条件时,AOF文件重写就会触发:

增长比例:(aof_current_size - aof_base_size) / aof_base_size > auto-aof-rewrite-percentage
文件大小:aof_current_size > auto-aof-rewrite-min-size
登入後複製

手动触发与自动触发的代码如下,同样在周期性方法serverCron中:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    /* 省略其他逻辑 */
    
    /* 如果用户请求进行AOF文件重写时,Redis正在执行RDB持久化,Redis会安排在RDB持久化完成后执行AOF文件重写,
     * 如果aof_rewrite_scheduled为true,说明需要执行用户的请求 */
    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方式,这一策略由配置参数aof-use-rdb-preamble(使用RDB作为AOF文件的前半段)控制,默认关闭(no),设置为yes可开启。所以,在AOF重写过程中文件的写入会有两种不同的方式。当aof-use-rdb-preamble的值是:

  • no:按照AOF格式写入命令,与4.0前版本无差别;
  • yes:先按照RDB格式写入数据状态,然后把重写期间AOF缓冲区的内容以AOF格式写入,文件前半部分为RDB格式,后半部分为AOF格式。

结合源码(6.0版本,源码太多这里不贴出,可参考aof.c)及参考资料,绘制AOF重写(BGREWRITEAOF)流程图:
Redis深入學習之詳解持久化原理
结合上图,总结一下AOF文件重写的流程:

  • rewriteAppendOnlyFileBackground开始执行,检查是否有正在进行的AOF重写或RDB持久化子进程:如果有,则退出该流程;如果没有,则继续创建接下来父子进程间数据传输的通信管道。执行fork()操作,成功后父子进程分别执行不同的流程。
  • 父进程:

    • 记录子进程信息(pid)、时间戳等;
    • 继续响应其他客户端请求;
    • 收集AOF重写期间的命令,追加至aof_rewrite_buffer;
    • 等待并向子进程同步aof_rewrite_buffer的内容;
  • 子进程:

    • 修改目前程序名稱,建立重寫所需的臨時文件,呼叫rewriteAppendOnlyFile函數;
    • 根據aof-use-rdb-preamble配置,以RDB或AOF方式寫入前半部分,並同步至硬碟;
    • 從父程序接收增量AOF指令,以AOF方式寫入後半部分,並同步至硬碟;
    • 重命名AOF檔,子進程退出。

資料載入

Redis啟動後透過loadDataFromDisk函數執行資料載入工作。這裡要注意,雖然持久化方式可以選擇AOF、RDB或兩者兼用,但是資料載入時必須做出選擇,兩種方式各自載入一遍就亂套了。

理論上,AOF持久化比RDB有更好的即時性,當開啟了AOF持久化方式,Redis在資料載入時優先考慮AOF方式。而且,Redis 4.0版本後AOF支援了混合持久化,載入AOF檔需要考慮版本相容性。 Redis資料載入流程如下圖所示:
Redis深入學習之詳解持久化原理
在AOF方式下,開啟混合持久化機制產生的檔案是“RDB頭AOF尾”,未開啟時產生的文件全部為AOF格式。考慮兩種檔案格式的相容性,如果Redis發現AOF檔案為RDB頭,會使用RDB資料載入的方法讀取並還原前半部;然後再使用AOF方式讀取並恢復後半部。由於AOF格式儲存的資料為RESP協定指令,Redis採用偽客戶端執行指令的方式來復原資料。

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

  • yes:則盡可能多的加載數據,並以日誌的方式通知使用者;
  • no:則以系統錯誤的方式崩潰,並禁止啟動,需要使用者修復檔案後再重新啟動。

總結

Redis提供了兩個持久化的選擇:RDB支援以特定的實踐間隔為資料集產生時間點快照;AOF把Redis Server收到的每條寫入指令持久化到日誌中,待Redis重啟時透過重播指令恢復資料。日誌格式為RESP協議,對日誌檔案只做append操作,無損壞風險。且當AOF檔案過大時可以自動重寫壓縮檔案。

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

RDB vs AOF

RDB優點

  • RDB是一個緊湊壓縮的二進位文件,代表Redis在某一個時間點上的資料快照,非常適合用於備份、全量複製等場景。
  • RDB對災難復原、資料遷移非常友好,RDB檔案可以轉移至任何需要的地方並重新載入。
  • RDB是Redis資料的記憶體快照,資料復原速度較快,相較於AOF的命令重播有著更高的效能。

RDB缺點

  • RDB方式無法做到即時或秒級持久化。因為持​​久化過程是透過fork子進程後由子進程完成的,子進程的記憶體只是在fork操作那一時刻父進程的資料快照,而fork操作後父進程持續對外服務,內部資料時刻變更,子進程的數據不再更新,兩者始終存在差異,所以無法做到即時性。
  • RDB持久化過程中的fork操作,會導致記憶體佔用加倍,而且父進程資料越多,fork過程越長。
  • Redis請求高並發可能會頻繁命中save規則,導致fork操作及持久化備份的頻率不可控;
  • RDB檔案有檔案格式要求,不同版本的Redis會對檔案格式進行調整,有舊版本無法相容新版本的問題。

AOF優點

  • AOF持久化有更好的即時性,我們可以選擇三種不同的方式(appendfsync):no、every second、always,every second作為預設的策略具有最佳的效能,極端情況下可能會遺失一秒鐘的資料。
  • AOF檔案只有append操作,無複雜的seek等檔案操作,沒有損壞風險。即使最後寫入資料被截斷,也很容易使用redis-check-aof工具修復;
  • 當AOF檔變大時,Redis可在背景自動重寫。重寫過程中舊檔案會持續寫入,重寫完成後新檔案將變得更小,並且重寫過程中的增量命令也會append到新檔案。
  • AOF檔案以已於理解與解析的方式包含了對Redis中資料的所有操作命令。即使不小心錯誤的清除了所有數據,只要沒有對AOF檔案重寫,我們就可以透過移除最後一條指令找回所有資料。
  • AOF已經支援混合持久化,檔案大小可以有效控制,並提高了資料載入時的效率。

AOF缺點

  • 對於相同的資料集合,AOF檔通常會比RDB檔大;
  • 在特定的fsync策略下,AOF會比RDB略慢。一般來講,fsync_every_second的效能仍然很高,fsync_no的效能與RDB相當。但在巨大的寫壓力下,RDB更能提供最大的低延時保障。
  • 在AOF上,Redis曾經遇到一些幾乎不可能在RDB上遇到的罕見bug。一些特殊的指令(如BRPOPLPUSH)導致重新載入的資料與持久化之前不一致,Redis官方曾經在相同的條件下進行測試,但是無法復現問題。

使用建議

對RDB和AOF兩種持久化方式的工作原理、執行流程及優缺點了解後,我們來思考下,實際場景中應該怎麼權衡利弊,合理的使用兩種持久化方式。如果只是使用Redis作為快取工具,所有資料可以根據持久化資料庫進行重建,則可關閉持久化功能,做好預熱、快取穿透、擊穿、雪崩之類的防護工作即可。

一般情況下,Redis會承擔更多的工作,如分散式鎖定、排行榜、註冊中心等,持久化功能在災難復原、資料遷移方面將發揮較大的作用。建議遵循幾個原則:

  • 不要把Redis當作資料庫,所有資料都盡可能由應用程式服務自動重建。
  • 使用4.0以上版本Redis,使用AOF RDB混合持久化功能。
  • 合理規劃Redis最大佔用內存,防止AOF重寫或save過程中資源不足。
  • 避免單機部署多實例。
  • 生產環境多為叢集化部署,可在slave開啟持久化能力,讓master更好的對外提供寫入服務。
  • 備份檔案應自動上傳至異地機房或雲端存儲,做好災難備份。

關於fork()

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

  • 降低fork的頻率,例如可以手動來觸發RDB產生快照、與AOF重寫;
  • 控制Redis最大使用內存,防止fork耗時過長;
  • 使用更高效能的硬體;
  • 合理配置Linux的記憶體分配策略,避免因為物理記憶體不足導致fork失敗。

更多程式相關知識,請造訪:程式設計入門! !

以上是Redis深入學習之詳解持久化原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:segmentfault.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板