共计 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…