2024.06.08 更新

补充了思维导图:

image

AOF 日志

介绍

AOF(append-only file)是 Redis 持久化数据的一种策略

由于 Redis 的数据都存在内存,如果不做持久化,重启后数据就丢失了

AOF 的机制是:在执行完每一条写命令后,顺便将这条命令追加在 AOF 文件中

例如:

192.168.124.114:6379> set k1 v1
OK
192.168.124.114:6379> exit
[root@localhost redis-6.2.13]# cat appendonly.aof
*3  # 接下来的命令有三个字符串
$3  # 接下来的字符串有三个字节
set # set 命令
$2
k1
$2
v1

要配置和启用 AOF 持久化,可以在 Redis 配置文件中设置以下配置项:

  • appendonly yes:启用 AOF 持久化。

  • appendfilename:指定 AOF 文件的名称,默认为 appendonly.aof

  • appendfsync:设置 AOF 同步策略

当 Redis 重启或者断电恢复时,就可以使用 AOF 文件做重放,以达到恢复数据的目的

持久化策略

来看看 AOF 持久化的原理图:

image

AOF 有三种持久化策略:

  • always:每执行一次写命令,就执行一次 fsync,效率最低,安全性最高
  • everysec:执行一次写命令,将命令记录到 AOF 缓冲区,每个 1s 将缓冲区的数据刷新(fsync)到磁盘,效率中等,安全性较高
  • no:执行一次写命令,将命令记录到 AOF 缓冲区,由 OS 决定将缓冲区的数据刷新到磁盘(只 write,不 fsync),效率高,安全性低

AOF 重写

来看这一个例子:

192.168.124.114:6379> set k1 v1
OK
192.168.124.114:6379> set k1 v2
OK
192.168.124.114:6379> del k1
(integer) 1
192.168.124.114:6379> exit
[root@localhost redis-6.2.13]# cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$2
k1
$2
v1
*3
$3
set
$2
k1
$2
v2
*2
$3
del
$2
k1

事实上,对 k1 的修改完全没有必要全部记录下来,但是 AOF 还是将多余的操作记录下来了,这将浪费空间

而 AOF 重写就是用来解决这个问题的

AOF 重写的过程:

  • 新建一个 AOF 文件,记做 tmp
  • 遍历 Redis 中 所有的键值对,生成对应的执行命令(例如,有一个 string 类型的 k1,value 为 v1,就会在 tmp 中记下 SET k1 v1
  • 用 tmp 替换原来旧的 AOF 文件,这个过程是 原子的

这个重写过程存在问题:

如果在主进程执行,那么在重写过程中,主线程就无法接受新的写请求,会导致 tmp 与 Redis 的数据不一致

因此,为了保证在 AOF 的重写的过程中,主线程仍然可以接受新的写请求,重写操作实际上是由子 进程 完成的

这里给出 AOF 重写的完整过程:

  • 新建一个 AOF 文件,记做 tmp
  • 新建一个 AOF 重写缓冲区
  • fork 一个子进程
  • 子进程遍历 Redis 中所有的键值对,生成对应的执行命令
  • 重写过程中,如果有新的写入请求,主进程需要:
    • 写入到 data 区
    • 写入到 aof_buffer
    • 写入到 aof_rewrite_buffer
  • 当子进程的重写操作执行完毕,发送一个信号给主进程
  • 主进程收到信号后,调用信号处理函数
  • 信号处理函数会做以下事情:
    • 将 aof_rewrite_buffer 的数据追加到 tmp 中
    • 用 tmp 替换原来旧的 AOF 文件

AOF 重写(rewrite) 是一个有歧义的名字,该功能是通过读取 数据库(dataset)中 的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。

Redis 利用子进程 与 aof_rewrite_buffer 来实现重写时仍能接受新的写请求

为什么要 fork 一个子进程,子线程不行吗?

使用子进程的目的主要还是避免加锁带来的性能开销

fork 子进程会用到 写时拷贝,即使在重写过程中,主进程由于接受用户写请求而修改了部分数据,也不会影响到子进程的内存空间

只要修改的数据不多(即读多写少),那么子进程带来的性能开销不会很大,比线程加锁的性能开销更低

RDB 快照

介绍

Redis RDB(Redis Database Dump)是 Redis 数据持久化的一种方式,它用于将 Redis 数据快照保存到磁盘上,以便在服务器重启时可以重新加载数据。RDB 是一种 紧凑且高性能 的持久化方式,通常用于灾难恢复和 备份

持久化策略

# Unless specified otherwise, by default Redis will save the DB:
#   * After 3600 seconds (an hour) if at least 1 key changed
#   * After 300 seconds (5 minutes) if at least 100 keys changed
#   * After 60 seconds if at least 10000 keys changed

# You can set these explicitly by uncommenting the three following lines.

# save ""  禁用 RDB
save 3600 1
save 300 100
save 60 10000

save 3600 1 为例:如果一个小时内,Redis 执行了 1 条写命令,就触发 RDB 持久化

RDB 持久化也是 fork 一个子进程来处理,但相较于 AOF 来说,RDB 更紧凑,存放的是二进制数据,占用空间小,恢复速度更快

也可以手动执行 BGSAVE 来生成一个 RDB 快照

RDB 与 AOF 混合持久化

  • 如果单独使用 RDB,性能较好,恢复较快,但备份的粒度太粗,丢的数据可能较多
  • 如果单独使用 AOF,性能还行,备份粒度细,丢的数据少,但恢复速度慢

在 Redis4.0 引入了「混合持久化」,可以将 RDB 与 AOF 混合在一起使用

混合持久化,其实与 AOF 的重写很类似,就是把「重写」换成了 RDB 而已,重写过程生成的 tmp 实际上是 RDB 快照

也就是说,AOF 文件的 前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。

这样,既能保证恢复的速率,也可以保证数据的可靠性

image

开启混合持久化,只需要修改 aof-use-rdb-preamble 为 yes 即可

# When rewriting the AOF file, Redis is able to use an RDB preamble in the
# AOF file for faster rewrites and recoveries. When this option is turned
# on the rewritten AOF file is composed of two different stanzas:
#
#   [RDB file][AOF tail]
#
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
aof-use-rdb-preamble yes

注意:RDB 与 AOF 混合持久化这种方式,是建立在 AOF 持久化基础上的,本质上还是 AOF 持久化

可以看看这个 issue

其它

AOF VS. RDB

来看看 AOF 的优缺点:

优点:

  • 可以控制刷盘策略
  • 备份粒度细,丢失数据少
  • 如果 AOF 文件损坏,相较于 RDB 更容易恢复
  • 可读性较好,可以人为修改(撤销)错误命令

缺点:

  • 文件占用空间相对较大,重启恢复慢
  • 性能占用平均而言比 RDB 高(需要将数据转换成一条条 Redis Command)

再来看看 RDB 的优缺点:

  • 采用二进制编码,占用空间小,重启恢复快
  • 性能占用平均而言比 AOF 低

缺点:

  • 备份粒度粗,如果仅使用 RDB,可能丢失较多数据(取决于设置的备份间隔)
  • 如果 RDB 文件损坏,几乎无法恢复
  • fork 的频率比 AOF 高(AOF 只有重写时才会 fork,而 RDB fork 的频率取决于设置的备份间隔)

fork 期间,主进程是无法接受客户端的所有请求的,如果 dataset 较大,fork 需要更长的时间

Redis 重启后是怎么恢复数据的?

分三个情况讨论:

  • 单独启用 RDB
  • 单独启用 AOF
  • 同时启用 RDB 和 AOF

单独启用 RDB,或者单独启用 AOF,主进程会先读取 RDB 或者 AOF 文件到内存,做数据恢复,然后才能接受客户端的请求

而如果同时启用了 RDB 和 AOF,那么 Redis 会 优先使用 AOF 文件做恢复,因为 AOF 的备份粒度较细,更能保证数据的可靠性(以牺牲恢复速度为代价)

In the case both AOF and RDB persistence are enabled and Redis restarts the AOF file will be used to reconstruct the original dataset since it is guaranteed to be the most complete.

既然都优先使用 AOF 了,为什么还要使用 RDB?

Redis 官方给出的解释:

There are many users using AOF alone, but we discourage it since to have an RDB snapshot from time to time is a great idea for doing database backups, for faster restarts, and in the event of bugs in the AOF engine.

简单来说,RDB 更适合做数据备份,可以作为一种兜底方案,如果在 AOF 引擎中出现错误,可能会导致数据丢失。定期创建 RDB 快照可以在这种情况下提供一种恢复数据的方法。通过比较 RDB 快照和当前数据,可以找出丢失的数据并从快照中恢复。

Redis7.0 后的 AOF

从 Redis 7.0.0 版本开始,Redis 使用 多部分 AOF 机制(multi part AOF mechanism)。原来的单 AOF 文件被拆分为 基础文件(最多一个)和 增量文件(可能多个)。基础文件代表在 AOF 被重写时的数据初始快照(RDB 或 AOF 格式),增量文件包含自上次基础 AOF 文件创建以来的增量更改。所有这些文件都被放在一个单独的目录中,并由一个清单文件(manifest file)跟踪。

AOF 重写时,Redis 父进程打开一个新的增量 AOF 文件以继续写入。子进程执行重写逻辑并生成一个新的基础 AOF。Redis 将使用一个临时清单文件来跟踪新生成的基础文件和增量文件。当它们准备好时,Redis 将执行一个原子替换操作,使这个临时清单文件生效。

AOF 文件截断或者损坏了,怎么办?

如果 AOF 截断(truncated)了,在重启 Redis 会看到如下提示:

* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled

默认情况下,Redis 会继续运行,忽略 掉截断的部分,保证可用性

当然我们也可以通过修改 aof-load-truncated 来控制这个行为(yes or no)

通过提示信息定位到截断的部分,可以人工修改一下

如果 AOF 损坏了,在重启 Redis 会看到如下提示:

* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>

这种情况就需要使用 redis-check-aof 来修复了

redis-check-aof 工具会提示问题出现的位置,我们可以人工修改

The best thing to do is to run the redis-check-aof utility, initially without the –fix option, then understand the problem, jump to the given offset in the file, and see if it is possible to manually repair the file: The AOF uses the same format of the Redis protocol and is quite simple to fix manually. Otherwise it is possible to let the utility fix the file for us, but in that case all the AOF portion from the invalid part to the end of the file may be discarded, leading to a massive amount of data loss if the corruption happened to be in the initial part of the file.

如何在 Redis 从 RDB 切换到 AOF?

如果在仅启用 RDB 快照的情况下,想要启用 AOF,千万不要直接修改配置文件,然后重启,会造成数据丢失

准备工作

  • 备份 RDB 文件

在运行的 Redis Server 上迁移

  • 启用 AOF:
    redis-cli config set appendonly yes
    
  • 关闭 RDB(可选)
  • 更新 Redis 的配置文件,确保启用了 AOF(否则重启使用旧的配置文件,可能造成数据丢失)

如果要重启 Server,需要等到 AOF 持久化完毕以后才能重启(You can do that by watching INFO persistence, waiting for aof_rewrite_in_progress and aof_rewrite_scheduled to be 0, and validating that aof_last_bgrewrite_status is ok.)

Redis 大 key 对持久化有啥影响?

大 Key 指的是占用空间大的 K-V 键值对

如果启用了 AOF,并且持久化策略为 Always,那么每次对大 Key 写操作,fsync 的时间会很长,阻塞其它客户端的请求

在创建 RDB 快照,或者 AOF 重写时,需要 fork 子进程,由于大 Key 的存在,页表也会更大,fork 的时间会更长,阻塞其它客户端的请求

此外,在子进程创建 RDB 快照,重写 AOF 时,如果修改了大 Key,主进程会发生 写时拷贝,这个过程的时间可能较长,阻塞其它客户端的请求

除了影响持久化以外,还有以下影响:

  • 打满网络 IO:例如一个 1M 的大 key,如果 QPS 为 1k,那么每秒产生 1G 流量,网络 IO 压力大
  • 删除操作:如果直接 DEL 一个大 Key,可能阻塞较长时间,影响其它客户端的请求

参考资料