写点什么

你真的了解 Redis 的持久化机制吗?

作者:C++后台开发
  • 2022 年 7 月 29 日
  • 本文字数:3985 字

    阅读完需:约 13 分钟

2022 年 6 月的某一天,办公区的气氛格外的压抑,此时的小编吹着空调双眼微眯的站在同事胖虎身后,看着他满头大汗的将数据库的数据导入到 Redis。

经过小编的一番询问才知道,原来是他们生产 Redis 今天莫名其妙的挂了,而且因为是新项目,一会要给领导演示用,生产环境 Redis 就只部署了一个节点,这一挂不要紧,缓存进去的数据怎么办...

恰巧这个时候,小编听见老板办公室里传出了一声大喊,“胖虎,来给我演示一下项目”,五分钟后,满脸通红的胖虎从老板办公室里出来了。

这时小编突然想到,Redis 不是有持久化机制吗? 在和胖虎的交谈中发现,他竟然不知道 Redis 的持久化机制,小编就默默地装了个 13。

Redis 持久化

Redis 的持久化机制有两种,一种是快照(RDB),另一种是 AOF 日志。RDB 是一次全量备份,AOF 日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而 AOF 日志记录的是内存数据修改的指令记录文本。

RDB 快照

RDB 快照持久化是 Redis 默认开启的,它会根据配置策略将内存数据保存在一个名为 dmp.rdb 的二进制文件中。

在 redis.conf 配置文件中,可以配置 RDB 的持久化策略,系统默认是有三个保存策略(三个同时生效),如下:

  • save 900 1 900 秒内有 1 个键发生改变

  • save 300 10 30 秒内有 10 个键发生改变

  • save 60 10000 60 秒内有 10000 个键发生改变

我们也可以根据我们自己生产环境的具体情况进行配置。如果我们要关闭 RDB 快照,直接将配置文件中上述三个 save 注释掉即可。

写时复制机制(COW)

Redis 提供了 save 命令进行快照生成,但是 save 命令要阻塞主进程(执行客户端命令的线程),当需要生成快照的内存特别大时(几百个 G),需要的时间也会很长,甚至十几秒,所以这个时候如果阻塞主进程,会导致整个服务不可用,就得不偿失了。

针对这种情况,Redis 借助操作系统提供的写时复制技术,提供了 bgsave 命令,basave 命令可以在主进程的基础上,fork 一个子进程,子进程会共享主进程的代码段和数据段,相当于是在后台生成快照。

在 bgsave 子进程写入 RDB 数据时,如果主进程对这些数据也都是读操作,那么,主进程和 bgsave 子进程相互不影响。但是,如果主进程要修改一块数据,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB,而在这个过程中,主进程仍然可以直接修改原来的数据。

【文章福利】另外小编还整理了一些 C/C++后台开发教学视频,相关面试题,后台学习路线图免费分享,需要的可以自行添加:Q群:720209036 点击加入~ 群文件共享

小编强力推荐 C++后台开发免费学习地址:C/C++Linux服务器开发高级架构师/C++后台开发架构师​

演示

bgsave 会进行后台操作,不阻塞流程。 保存后会生成一个 dump.rdb 文件。因为二进制文件打开也是乱码,所以我们就不打开了。

优点

  • Redis 宕机后数据恢复快

  • 二进制文件体积小

缺点

  • 持久化策略可能会导致在宕机时数据丢失量较多

AOF(append-only file)

​AOF 持久化可以通过 redis.conf 文件中的 appendonly 参数控制是否开启。

  • appendonly no 默认情况下是 no, 将 no 改为 yes 即可开启 AOF 持久化方式,Redis 会将修改的每一条指令先记录到系统缓存,默认每隔一秒钟就把缓存刷新到 appendonly.aof 磁盘文件中 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。

演示

小编直接修改 redis.conf 文件中的 appendonly 参数,然后重启 redis,set 了几个键值对,等了好几秒,发现当前目录下并没有生成 appendonly.aof 文件。

后来通过直接在 redis-cli 执行 config set appendonly yes 命令,当前目录下才生成 appendonly.aof 文件。

​接下来我们打开 appendonly.aof 文件就能看到我们之前写入的键值对。

*2$6SELECT$10*3$3SET$11$12*3$3SET$4key1$4val1*3$3SET$43124$3124*3$3SET$5key14$4key2
复制代码

这是一种 resp 协议格式数据,星号后面的数字代表命令有多少个参数,$号后面的数字代表这个参数有几个字

AOF 持久化策略

  • appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全,不建议用,本身 Redis 就是借助的内存快的优势,要是每次操作都访问磁盘这个优势就没了。

  • appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据,AOF 默认会走在这个配置。

  • appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择,发生宕机时,丢失的数据最多。

AOF 重写

假设我们执行了两个命令 set key1 1set key1 2,在开启 AOF 持久化的情况下,这两个命令都会被记录到 AOF 文件中,但是我们在宕机后重启恢复数据的时候是不是就只需要执行 set key1 2 就可以了,之前的 set key1 1 在恢复数据的时候忽略掉也没有任何影响,并且因为 AOF 文件随着时间的推移,体积会越来越大,重写 AOF 就显得尤为重要。

手动触发后台重写,redis 客户端执行命令:bgrewriteaof

Redis 提供了两个配置参数控制 AOF 自动重写频率(redis.conf)。

  • auto‐aof‐rewrite‐min‐size 64mb //aof 文件至少要达到 64M 才会自动重写,文件太小恢复速度本来就 很快

  • auto‐aof‐rewrite‐percentage 100 //aof 文件自上一次重写后文件大小增长了 100%则再次触发重写

触发重写时 Redis 会通过 fork 主进程,生成一个子进程去做,这么做的原因是因为重写一般意味着要有大量的 I/O 操作,非常耗时,如果用主进程去重写会导致主进程长时间的阻塞,影响 Redis 执行客户端命令,甚至导致服务长时间不可用,但是子进程去重写就意味着主进程仍然可以接收、处理客户端指令,就会导致重写后的 AOF 文件和服务器当前的数据库状态不一致。

例如:子进程 fork 主进程时,内存中只有 key1 这个键,但是当开始重写后,客户端又多 set 了 key2、key3,这就导致了重写的 AOF 文件和数据库不一致。

所以 Redis 在 AOF 重写时加了一个 AOF 重写缓冲区,当有新命令执行的时候,Redis 会将命令发送到 AOF 缓冲区和 AOF 重写缓冲区。

执行完重写任务后,子进程会向主进程发送一个信号,主进程收到信号后会执行下面两个操作(以下两个操作会阻塞主进程):

  • 将 AOF 重写缓冲区中的所有内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。

  • 对新的 AOF 文件进行改名,原子的覆盖现有 AOF 文件,完成新旧文件的替换

虽然只有上面两个操作会阻塞 Redis 主进程,但是很显然它已经将重写对性能的影响降到了最低。但是如果你想再对其进行优化,这里有两个解决方案。

  1. 服务低峰期定时重写

  2. 使用 ssd,提升持久化效率

注意:AOF 在重写时,为了避免执行命令时造成的客户端缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能会有多个元素的键时,会先检查键所包含的元素数量,如果元素的数量超过了 redis.h/REDIS_AOF_REWRITe_ITeMS_PER_CMD 常量的值(默认 64 个元素),那么重写程序将会使用多条命令来记录键的值,而不是单单只用一条命令。

优点

  • 数据完整性要高于 RDB,默认情况下,最多丢失 1 秒钟的数据。

缺点

  • Redis 宕机后数据恢复慢

  • 文件体积大

混合持久化(sence redis4.0)

Redis 宕机恢复时,为了提高数据的完整程度,我们通常使用 AOF 日志进行恢复,但是使用 AOF 日志性能相对 RDB 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。 Redis4.0 为了解决这个问题,推出了混合持久化,说白了,混合持久化就是结合了 RDB 与 AOF 两种持久化方式的优势。

启混合模式后,AOF 在重写时会将 AOF 文件中的数据以 RDB 文件的二进制格式写入当前 AOF 文件中,之后的新的写操作继续以 AOF 文件的格式进行追加。当 redis 宕机重启的时候,加载 AOF 文件进行恢复数据:先加载 RDB 的部分再加载剩余的 AOF 内容,因此重启效率大幅得到提升。

通过修改 redis.conf 文件中的以下配置开启混合持久化(必须先开启 AOF 持久化):

  • aof‐use‐rdb‐preamble yes

总结

  • RDB 文件会保存 Redis 中所有的键值对数据

  • save 生成 RDB 文件会阻塞主进程

  • bgsave 命令后台生成 RDB,不会阻塞主进程

  • RDB 文件体积小,保存的是二进制数据

  • RDB 通过配置策略执行,可能会丢失部分数据

  • RDB 文件在 Redis 宕机后恢复快


  • AOF 文件保存的是客户端执行的命令

  • AOF 文件体积大,恢复慢,但是默认配置下数据比 RDB 要全很多

  • Redis 宕机重启后默认是用 AOF 方式进行恢复

  • AOF 文件中的命令是以 Resp 协议格式保存的

  • 命令会先保存到缓冲区,再定期同步到 AOF 文件

  • AOF 会根据配置策略自动的对 AOF 文件进行重写,以降低文件体积

  • AOF 重写时会 fork 一个子进程去执行,同时会有一个重写缓冲区,用来保存重写时主进程修改的键

  • AOF 重写时,最后会生成一个新的 AOF 文件,覆盖原有的文件

  • 将重写缓冲区的内容写入到 AOF 与替换新旧 AOF 文件时会阻塞主进程


  • 混合持久化是 RDB 与 AOF 的优势结合所产生的

  • 混合持久化本质还是用的 AOF 文件

  • 混合持久化使用的前提是开启 AOF 持久化

  • 混合持久化在重写 AOF 文件时会将数据直接写成 RDB 的二进制格式,之后新的命令还是以 AOF 文件 Resp 协议格式进行保存

fsync

为了提高文件的写入效率,linux 系统在做持久化时,会调用 write 函数,先将要写入文件的数据保存在一个内存缓冲区里就会直接返回,等到缓冲区被填满,或者超过了指定的时限(一般是 30 秒)后,才会将缓冲区的数据写入到磁盘。

这种做法虽然提高了写入效率,但是如果发生宕机,就有可能会导致缓冲区中还未写入磁盘的数据丢失。

为此,linux 提供了 fsync 函数,它会强制并阻塞等待系统将数据写入到磁盘,从而保证数据的安全性

子进程

为什么 Redis 会 fork 一个子进程而不用子线程,是因为子进程可以携带主进程的数据副本,可以避免在不使用锁的情况下,保证数据安全。

注:子进程在重写期间,主进程还是会接收并处理客户端命令,会导致子进程与主进程数据不一致。

参考资料

​推荐一个零声教育 C/C++后台开发的免费公开课程,个人觉得老师讲得不错,分享给大家:C/C++后台开发高级架构师,内容包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习


原文:你真的了解Redis的持久化机制吗? - 掘金

用户头像

还未添加个人签名 2022.05.06 加入

还未添加个人简介

评论

发布
暂无评论
你真的了解Redis的持久化机制吗?_数据库_C++后台开发_InfoQ写作社区