redis持久化
参考:
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
面试造飞机系列:面对Redis持久化连环Call,你还顶得住吗?
快照持久化
快照持久化,顾名思义,就是通过拍摄快照的方式实现数据的持久化,redis可以在某个时间点上对内存中的数据创建一个副本文件,
副本文件中的数据在redis重启时会被自动加载,我们也可以将副本文件拷贝到其他地方一样可以使用。
如何配置快照持久化
redis中的快照持久化默认是开启的,redis.conf中相关配置主要有如下几项:
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
dbfilename dump.rdb
dir ./
前面三个save相关的选项表示备份的频率,分别表示900秒内至少一个键被更改则进行快照,300秒内至少10个键被更改则进行快照,60秒内至少10000个键被更改则进行快照,
stop-writes-on-bgsave-error表示在快照创建出错后,是否继续执行写命令,
rdbcompression则表示是否对快照文件进行压缩,
dbfilename表示生成的快照文件的名字,
dir则表示生成的快照文件的位置,
在redis中,快照持久化默认就是开启的。我们可以通过如下步骤验证快照持久化的效果:
1.进入redis安装目录,如果有dump.rdb文件,先将之删除。如下:
![p299]()
2.启动redis,随便向redis中存储几个数据,然后关闭redis并退出,如下:
[root@localhost redis-4.0.8]# redis-server redis.conf
[root@localhost redis-4.0.8]# redis-cli
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> SHUTDOWN
not connected> exit
3.退出来后,我们发现刚刚删掉的dump.rdb文件又回来了,这就是生成的备份文件。
4.此时再次启动redis并进入,发现刚刚存储的数据都还在,这是因为redis在启动时加载了dump.rdb中的数据。好了,关闭redis并退出。
5.将redis目录下的dump.rdb文件删除。
6.再次启动redis并进入到控制台,所有的数据都不存在了。
快照持久化操作流程
通过上面的介绍,小伙伴们对快照持久化都有一个大致的认识了,那么这个东西到底是怎么运行的?持久化的时机是什么?我们来仔细扒一扒。
1.在redis运行过程中,我们可以向redis发送一条save命令来创建一个快照,save是一个阻塞命令,
redis在接收到save命令之后,开始执行备份操作之后,在备份操作执行完毕之前,将不再处理其他请求,其他请求将被挂起,因此这个命令我们用的不多。
save命令执行如下:
127.0.0.1:6379> SAVE
OK
2.在redis运行过程中,我们也可以发送一条bgsave命令来创建一个快照,不同于save命令,
bgsave命令会fork一个子进程,然后这个子进程负责执行将快照写入硬盘,而父进程则继续处理客户端发来的请求,这样就不会导致客户端命令阻塞了。如下:
127.0.0.1:6379> BGSAVE
Background saving started
3.如果我们在redis.conf中配置了如下选项:
save 900 1
save 300 10
save 60 10000
那么当条件满足时,比如900秒内有一个key被操作了,那么redis就会自动触发bgsava命令进行备份。我们可以根据实际需求在redis.conf中配置多个这种触发规则。
4.还有一种情况也会触发save命令,那就是我们执行shutdown命令时,当我们用shutdown命令关闭redis时,
此时也会执行一个save命令进行备份操作,并在备份操作完成后将服务器关闭。
5.还有一种特殊情况也会触发bgsave命令,就是在主从备份的时候。当从机连接上主机后,会发送一条sync命令来开始一次复制操作,
此时主机会开始一次bgsave操作,并在bgsave操作结束后向从机发送快照数据实现数据同步。
快照持久化的缺点
快照持久化有一些缺点,比如save命令会发生阻塞,bgsave虽然不会发生阻塞,但是fork一个子进程又要耗费资源,在一些极端情况下,fork子进程的时间甚至超过数据备份的时间。
定期的持久化也会让我们存在数据丢失的风险,最坏的情况我们可能丢失掉最近一次备份到当下的数据,具体丢失多久的数据,要看我们项目的承受能力,我们可以根据项目的承受能力配饰save参数。
OK,快照持久化我们就介绍这么多,更多资料小伙伴们可以参考官方文档http://www.redis.net.cn/tutorial/3501.html。小伙伴在看官方文档时,有什么问题欢迎留言讨论。
RDB持久化原理
RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。
那么RDB持久化的过程,相当于在执行bgsave命令。该命令执行过程如下图所示

如图所示,主线程需要调用系统函数fork(),构建出一个子进程进行持久化!很不幸的是,在构建子进程的过程中,父进程就会阻塞,无法响应客户端的请求!
而且,在测试中发现,fork函数在虚拟机上较慢,真机上较快。考虑到现在都是部署在docker容器中,很少部署在真机上,为了性能,master不建议打开RDB持久化!
AOF持久化
与快照持久化不同,AOF持久化是将被执行的命令写到aof文件末尾,在恢复时只需要从头到尾执行一遍写命令即可恢复数据,
AOF在redis中默认也是没有开启的,需要我们手动开启,开启方式如下:
打开redis.conf配置文件,修改appendonly属性值为yes,如下:
appendonly yes
另外几个和AOF相关的属性如下:
appendfilename "appendonly.aof"
# appendfsync always
appendfsync everysec
# appendfsync no
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
这几个属性的含义分别如下:
1.appendfilename表示生成的AOF备份文件的文件名。
2.appendfsync表示备份的时机,always表示每执行一个命令就备份一次,everysec表示每秒备份一次,no表示将备份时机交给操作系统。
3.no-appendfsync-on-rewrite表示在对aof文件进行压缩时,是否执行同步操作。
4.最后两行配置表示AOF文件的压缩时机,这个我们一会再细说。
AOF备份的几个关键点
1.通过上面的介绍,小伙伴们了解到appendfsync的取值一共有三种,我们在项目中首选everysec,always选项会严重降低redis性能。
2.使用everysec,最坏的情况下我们可能丢失1秒的数据。
AOF文件的重写与压缩
AOF备份有很多明显的优势,当然也有劣势,那就是文件大小。
随着系统的运行,AOF的文件会越来越大,甚至把整个电脑的硬盘填满,AOF文件的重写与压缩机制可以在一定程度上缓解这个问题。
当AOF的备份文件过大时,我们可以向redis发送一条bgrewriteaof命令进行文件重写,如下:
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
(0.71s)
bgrewriteaof的执行原理和我们上文说的bgsave的原理一致,这里我就不再赘述,因此bgsave执行过程中存在的问题在这里也一样存在。
bgrewriteaof也可以自动执行,自动执行时间则依赖于auto-aof-rewrite-percentage和auto-aof-rewrite-min-size配置,
auto-aof-rewrite-percentage 100表示当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,
如果之前没有重写,则以启动时的aof文件大小为依据,同时还要求AOF文件的大小至少要大于64M(auto-aof-rewrite-min-size 64mb)。
最佳实践
1.如果redis只做缓存服务器,那么可以不使用任何持久化方式。
2.同时开启两种持久化方式,在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据, 因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整;
RDB的数据不完整时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢? 作者建议不要,
因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
3.因为RDB文件只用作后备用途,建议只在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
4.如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
5.如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。
代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。
AOF持久化原理
AOF 持久化的实现

如上图所示,AOF 持久化功能的实现可以分为命令追加( append )、文件写入( write )、文件同步( sync )、文件重写(rewrite)和重启加载(load)。其流程如下:
所有的写命令会追加到 AOF 缓冲中。
AOF 缓冲区根据对应的策略向硬盘进行同步操作。
随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
当 Redis 重启时,可以加载 AOF 文件进行数据恢复。
命令追加
当 AOF 持久化功能处于打开状态时,Redis 在执行完一个写命令之后,会以协议格式(也就是RESP,即 Redis 客户端和服务器交互的通信协议 )将被执行的写命令追加到 Redis 服务端维护的 AOF 缓冲区末尾。
比如说 SET mykey myvalue 这条命令就以如下格式记录到 AOF 缓冲中。
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
Redis 协议格式本文不再赘述,AOF之所以直接采用文本协议格式,是因为所有写入命令都要进行追加操作,直接采用协议格式,避免了二次处理开销。
文件写入和同步
Redis 每次结束一个事件循环之前,它都会调用 flushAppendOnlyFile 函数,判断是否需要将 AOF 缓存区中的内容写入和同步到 AOF 文件中。
flushAppendOnlyFile 函数的行为由 redis.conf 配置中的 appendfsync 选项的值来决定。该选项有三个可选值,分别是 always、 everysec 和 no:
always:Redis 在每个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件,并且同步 AOF 文件,所以 always 的效率是 appendfsync 选项三个值当中最差的一个,
但从安全性来说,也是最安全的。当发生故障停机时,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。
everysec:Redis 在每个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件中,并且每隔一秒就要在子线程中对 AOF 文件进行一次同步。
从效率上看,该模式足够快。当发生故障停机时,只会丢失一秒钟的命令数据。
no:Redis 在每一个事件循环都要将 AOF 缓冲区中的所有内容写入到 AOF 文件。
而 AOF 文件的同步由操作系统控制。这种模式下速度最快,但是同步的时间间隔较长,出现故障时可能会丢失较多数据。
Linux 系统下 write 操作会触发延迟写( delayed write )机制。Linux 在内核提供页缓存区用来提供硬盘 IO 性能。
write 操作在写入系统缓冲区之后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或者达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
而 fsync 针对单个文件操作,对其进行强制硬盘同步, fsync 将阻塞直到写入磁盘完成后返回,保证了数据持久化。
appendfsync的三个值代表着三种不同的调用 fsync的策略。调用 fsync周期越频繁,读写效率就越差,但是相应的安全性越高,发生宕机时丢失的数据越少。
有关 Linux 的I/O和各个系统调用的作用如下图所示。具体内容可以查看《聊聊 Linux I/O》一文。

AOF 数据恢复
AOF 文件里边包含了重建 Redis 数据所需的所有写命令,所以 Redis 只要读入并重新执行一遍 AOF 文件里边保存的写命令,就可以还原 Redis 关闭之前的状态。

Redis 读取 AOF 文件并且还原数据库状态的详细步骤如下:
创建一个不带网络连接的的伪客户端( fake client),因为 Redis 的命令只能在客户端上下文中执行,
而载入 AOF 文件时所使用的的命令直接来源于 AOF 文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行 AOF 文件保存的写命令,
伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样的。
从 AOF 文件中分析并取出一条写命令。
使用伪客户端执行被读出的写命令。
一直执行步骤 2 和步骤3,直到 AOF 文件中的所有写命令都被处理完毕为止。
当完成以上步骤之后,AOF 文件所保存的数据库状态就会被完整还原出来。
AOF 重写
因为 AOF 持久化是通过保存被执行的写命令来记录 Redis 状态的,所以随着 Redis 长时间运行,AOF 文件中的内容会越来越多,文件的体积也会越来越大,
如果不加以控制的话,体积过大的 AOF 文件很可能对 Redis 甚至宿主计算机造成影响。
为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写( rewrite) 功能。
通过该功能,Redis 可以创建一个新的 AOF 文件来替代现有的 AOF 文件。
新旧两个 AOF 文件所保存的 Redis 状态相同,但是新的 AOF 文件不会包含任何浪费空间的荣誉命令,所以新 AOF 文件的体积通常比旧 AOF 文件的体积要小得很多。

如上图所示,重写前要记录名为 list的键的状态,AOF 文件要保存五条命令,而重写后,则只需要保存一条命令。
AOF 文件重写并不需要对现有的 AOF 文件进行任何读取、分析或者写入操作,而是通过读取服务器当前的数据库状态来实现的。
首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是 AOF 重写功能的实现原理。
在实际过程中,为了避免在执行命令时造成客户端输入缓冲区溢出,AOF 重写在处理列表、哈希表、集合和有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,
如果数量超过 REDISAOFREWRITEITEMSPER_CMD ( 一般为64 )常量,则使用多条命令记录该键的值,而不是一条命令。
rewrite的触发机制主要有一下三个:
手动调用 bgrewriteaof 命令,如果当前有正在运行的 rewrite 子进程,则本次rewrite 会推迟执行,否则,直接触发一次 rewrite。
通过配置指令手动开启 AOF 功能,如果没有 RDB 子进程的情况下,会触发一次 rewrite,将当前数据库中的数据写入 rewrite 文件。
在 Redis 定时器中,如果有需要退出执行的 rewrite 并且没有正在运行的 RDB 或者 rewrite 子进程时,触发一次或者 AOF 文件大小已经到达配置的 rewrite 条件也会自动触发一次。
AOF 后台重写
AOF 重写函数会进行大量的写入操作,调用该函数的线程将被长时间阻塞,所以 Redis 在子进程中执行 AOF 重写操作。
子进程进行 AOF 重写期间,Redis 进程可以继续处理客户端命令请求。
子进程带有父进程的内存数据拷贝副本,在不适用锁的情况下,也可以保证数据的安全性。
但是,在子进程进行 AOF 重启期间,Redis接收客户端命令,会对现有数据库状态进行修改,从而导致数据当前状态和 重写后的 AOF 文件所保存的数据库状态不一致。
为此,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。

当子进程完成 AOF 重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:
将 AOF 重写缓冲区中的所有内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
对新的 AOF 文件进行改名,原子地覆盖现有 AOF 文件,完成新旧文件的替换
继续处理客户端请求命令。
在整个 AOF 后台重写过程中,只有信号处理函数执行时会对 Redis 主进程造成阻塞,在其他时候,AOF 后台重写都不会阻塞主进程。

redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
redis 持久化的两种方式
RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。
如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,
然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。
如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 AOF 来重新构建数据,因为 AOF 中的数据更加完整。
RDB 优缺点
RDB 会生成多个数据文件,每个数据文件都代表了某一个时刻中 redis 的数据,这种多个数据文件的方式,非常适合做冷备,
可以将这种完整的数据文件发送到一些远程的安全存储上去,比如说 Amazon 的 S3 云服务上去,在国内可以是阿里云的 ODPS 分布式存储上,以预定好的备份策略来定期备份 redis 中的数据。
RDB 对 redis 对外提供的读写服务,影响非常小,可以让 redis 保持高性能,因为 redis 主进程只需要 fork 一个子进程,让子进程执行磁盘 IO 操作来进行 RDB 持久化即可。
相对于 AOF 持久化机制来说,直接基于 RDB 数据文件来重启和恢复 redis 进程,更加快速。
如果想要在 redis 故障时,尽可能少的丢失数据,那么 RDB 没有 AOF 好。
一般来说,RDB 数据快照文件,都是每隔 5 分钟,或者更长时间生成一次,这个时候就得接受一旦 redis 进程宕机,那么会丢失最近 5 分钟的数据。
RDB 每次在 fork 子进程来执行 RDB 快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒。
AOF 优缺点
AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次fsync操作,最多丢失 1 秒钟的数据。
AOF 日志文件以 append-only 模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复。
AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
因为在 rewrite log 的时候,会对其中的指令进行压缩,创建出一份需要恢复数据的最小日志出来。
在创建新日志文件的时候,老的日志文件还是照常写入。当新的 merge 后的日志文件 ready 的时候,再交换新老日志文件即可。
AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。
比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据。
对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。
AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的。(如果实时写入,那么 QPS 会大降,redis 性能会大大降低)
以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。
所以说,类似 AOF 这种较为复杂的基于命令日志 / merge / 回放的方式,比基于 RDB 每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有 bug。
不过 AOF 就是为了避免 rewrite 过程导致的 bug,因此每次 rewrite 并不是基于旧的指令日志进行 merge 的,而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。
RDB 和 AOF 到底该如何选择
不要仅仅使用 RDB,因为那样会导致你丢失很多数据;
也不要仅仅使用 AOF,因为那样有两个问题:
第一,你通过 AOF 做冷备,没有 RDB 做冷备来的恢复速度更快;
第二,RDB 每次简单粗暴生成数据快照,更加健壮,可以避免 AOF 这种复杂的备份和恢复机制的 bug;
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制,用 AOF 来保证数据不丢失,作为数据恢复的第一选择;
用 RDB 来做不同程度的冷备,在 AOF 文件都丢失或损坏不可用的时候,还可以使用 RDB 来进行快速的数据恢复。


浙公网安备 33010602011771号