关于redis:Redis专题持久化方式之RDB

35次阅读

共计 7704 个字符,预计需要花费 20 分钟才能阅读完成。

公众号搜寻“码路印记”

写在后面

Redis 是一个内存数据库,也就是说所有的数据将保留在内存中,这与传统的 MySQL、Oracle、SqlServer 等关系型数据库间接把数据保留到硬盘相比,Redis 的读写效率十分高。然而保留在内存中也有一个很大的缺点,一旦断电或者宕机,内存数据库中的内容将会全副失落。为了补救这一缺点,Redis 提供了把内存数据长久化到硬盘文件,以及通过备份文件来复原数据的性能。

Redis 反对两种形式的长久化:RDB 快照文件和 AOF。本文先介绍 RDB 快照形式的工作原理、优缺点等方面进行论述,写完 AOF 形式后再比照两者的优缺点,联合前辈总结给出生产实践,心愿可能对你了解 Redis 的长久化机制带来帮忙。通过本文你将理解到以下内容:~~

RDB 快照用官网的话来说:RDB 长久化计划是依照指定工夫距离对你的数据集生成的工夫点快照。它是 Redis 数据库中数据的内存快照,它是一个二进制文件(默认名称为:dump.rdb,可批改),存储了文件生成时 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 ""。以下为几种配置的阐明:

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 为例剖析一下整个流程。

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 为上次长久化后产生批改的次数。

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 文件的文件格式,本着一图胜千言的理念,我依照源码流程绘制了下图。

补充阐明一下,上图右下角“遍历以后数据库的键值对并写入”这个环节会依据不同类型的 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 会对文件格式进行调整,存在老版本无奈兼容新版本的问题。

参考文献

  • Redis 官网文档:《Redis Persistence》https://redis.io/topics/persi…
  • 《Redis RDB Persistence Details》https://programming.vip/docs/…
  • 一文看懂 Redis 的长久化原理:https://programming.vip/docs/…
  • Redis 详解(六)—— RDB 长久化:https://www.cnblogs.com/ysoce…

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

正文完
 0