Redis——持久化(RDB与AOF)原理

时间:2021-04-25 00:50:59


摘要

Redis 的读写都是在内存中,所以它的性能较高,但在内存中的数据会随着服务器的重启而丢失,为了保证数据不丢失,我们需要将内存中的数据存储到磁盘,以便 Redis 重启时能够从磁盘中恢复原有的数据,而整个过程就叫做 Redis 持久化。Redis 持久化也是 Redis 和 Memcached 的主要区别之一,因为 Memcached 不具备持久化功能。

一、Redis持久化的几种方式

Redis 持久化拥有以下三种方式:

  • 快照方式(RDB, Redis DataBase)将某一个时刻的内存数据,以二进制的方式写入磁盘;
  • 文件追加方式(AOF, Append Only File),记录所有的操作命令,并以文本的形式追加到文件中;
  • 混合持久化方式,Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,又能减低数据丢失的风险。

二、Redis持久化RDB方式

RDB(Redis DataBase)是将某一个时刻的内存快照(Snapshot),以二进制的方式写入磁盘的过程。

2.1 手动触发RDB

手动触发持久化的操作有两个: ​​save​​​ 和 ​​bgsave​​ ,它们主要区别体现在:是否阻塞 Redis 主线程的执行。

save 命令:在客户端中执行 ​​save​​ 命令,就会触发 Redis 的持久化,但同时也是使 Redis 处于阻塞状态,直到 RDB 持久化完成,才会响应其他客户端发来的命令,所以在生产环境一定要慎用。​​save​​ 命令使用如下:

Redis——持久化(RDB与AOF)原理

从图片可以看出,当执行完 ​​save​​​ 命令之后,持久化文件 ​​dump.rdb​​​ 的修改时间就变了,这就表示 ​​save​​​ 成功的触发了 RDB 持久化。 ​​save​​ 命令执行流程,如下图所示:

Redis——持久化(RDB与AOF)原理

bgsave 命令:bgsave(background save)既后台保存的意思, 它和 ​​save​​​ 命令最大的区别就是 ​​bgsave​​​ 会 fork() 一个子进程来执行持久化,整个过程中只有在 fork() 子进程时有短暂的阻塞,当子进程被创建之后,Redis 的主进程就可以响应其他客户端的请求了,相对于整个流程都阻塞的 ​​save​​​ 命令来说,显然 ​​bgsave​​​ 命令更适合我们使用。 ​​bgsave​​​ 命令使用,如下图所示:​​bgsave​​ 执行流程,如下图所示:

Redis——持久化(RDB与AOF)原理

Redis——持久化(RDB与AOF)原理

2.2 自动触发RDB

说完了 RDB 的手动触发方式,下面来看如何自动触发 RDB 持久化? RDB 自动持久化主要来源于以下几种情况。

save m n

​save m n​​​ 是指在 m 秒内,如果有 n 个键发生改变,则自动触发持久化。 参数 m 和 n 可以在 Redis 的配置文件中找到,例如,​​save 60 1​​​ 则表明在 60 秒内,至少有一个键发生改变,就会触发 RDB 持久化。 自动触发持久化,本质是 Redis 通过判断,如果满足设置的触发条件,自动执行一次 ​​bgsave​​ 命令。 注意:当设置多个 save m n 命令时,满足任意一个条件都会触发持久化。 例如,我们设置了以下两个 save m n 命令:

  • save 60 10
  • save 600 1

当 60s 内如果有 10 次 Redis 键值发生改变,就会触发持久化;如果 60s 内 Redis 的键值改变次数少于 10 次,那么 Redis 就会判断 600s 内,Redis 的键值是否至少被修改了一次,如果满足则会触发持久化。

flushall

​flushall​​​ 命令用于清空 Redis 数据库,在生产环境下一定慎用,当 Redis 执行了 ​​flushall​​ 命令之后,则会触发自动持久化,把 RDB 文件清空。 执行结果如下图所示:

Redis——持久化(RDB与AOF)原理

主从同步触发

在 Redis 主从复制中,当从节点执行全量复制操作时,主节点会执行 ​​bgsave​​ 命令,并将 RDB 文件发送给从节点,该过程会自动触发 Redis 持久化。

2.3 Redis RDB的配置说明

合理的设置 RDB 的配置,可以保障 Redis 高效且稳定的运行,下面一起来看 RDB 的配置项都有哪些?RDB 配置参数可以在 Redis 的配置文件中找见,具体内容如下:

# RDB 保存的条件
save 900 1
save 300 10
save 60 10000

# bgsave 失败之后,是否停止持久化数据到磁盘,yes 表示停止持久化,no 表示忽略错误继续写文件。
stop-writes-on-bgsave-error yes

# RDB 文件压缩
rdbcompression yes

# 写入文件和读取文件时是否开启 RDB 文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。
rdbchecksum yes

# RDB 文件名
dbfilename dump.rdb

# RDB 文件目录
dir ./

其中比较重要的参数如下列表: ① save 参数 它是用来配置触发 RDB 持久化条件的参数,满足保存条件时将会把数据持久化到硬盘。 默认配置说明如下:

  • save 900 1:表示 900 秒内如果至少有 1 个 key 值变化,则把数据持久化到硬盘;
  • save 300 10:表示 300 秒内如果至少有 10 个 key 值变化,则把数据持久化到硬盘;
  • save 60 10000:表示 60 秒内如果至少有 10000 个 key 值变化,则把数据持久化到硬盘。

② rdbcompression 参数 它的默认值是 ​​yes​​ 表示开启 RDB 文件压缩,Redis 会采用 LZF 算法进行压缩。如果不想消耗 CPU 性能来进行文件压缩的话,可以设置为关闭此功能,这样的缺点是需要更多的磁盘空间来保存文件。 ③ rdbchecksum 参数 它的默认值为 ​​yes​​ 表示写入文件和读取文件时是否开启 RDB 文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。

Redis 中可以使用命令查询当前配置参数。查询命令的格式为:​​config get xxx​​​ ,例如,想要获取 RDB 文件的存储名称设置,可以使用 ​​config get dbfilename​​ ,执行效果如下图所示:

Redis——持久化(RDB与AOF)原理

查询 RDB 的文件目录,可使用命令 ​​config get dir​​ ,执行效果如下图所示:

Redis——持久化(RDB与AOF)原理

设置 RDB 的配置,可以通过以下两种方式:

  • 手动修改 Redis 配置文件;
  • 使用命令行设置,例如,使用 ​​config set dir "/usr/data"​​ 就是用于修改 RDB 的存储目录。

注意:手动修改 Redis 配置文件的方式是全局生效的,即重启 Redis 服务器设置参数也不会丢失,而使用命令修改的方式,在 Redis 重启之后就会丢失。但手动修改 Redis 配置文件,想要立即生效需要重启 Redis 服务器,而命令的方式则不需要重启 Redis 服务器。Redis 的配置文件位于 Redis 安装目录的根路径下,默认名称为 redis.conf。

2.4 RDB 文件恢复

当 Redis 服务器启动时,如果 Redis 根目录存在 RDB 文件 dump.rdb,Redis 就会自动加载 RDB 文件恢复持久化数据。 如果根目录没有 dump.rdb 文件,请先将 dump.rdb 文件移动到 Redis 的根目录。 验证 RDB 文件是否被加载 Redis 在启动时有日志信息,会显示是否加载了 RDB 文件,我们执行 Redis 启动命令:​​src/redis-server redis.conf​​ ,如下图所示:

Redis——持久化(RDB与AOF)原理

 从日志上可以看出, Redis 服务在启动时已经正常加载了 RDB 文件。提示:Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。

2.5 RDB的禁用持久化

禁用持久化可以提高 Redis 的执行效率,如果对数据丢失不敏感的情况下,可以在连接客户端的情况下,执行 ​​config set save ""​​ 命令即可禁用 Redis 的持久化,如下图所示:

Redis——持久化(RDB与AOF)原理

2.6 RDB 优缺点

1)RDB 优点

  • RDB 的内容为二进制的数据,占用内存更小,更紧凑,更适合做为备份文件;
  • RDB 对灾难恢复非常有用,它是一个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务恢复;
  • RDB 可以更大程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork() 一个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;
  • 与 AOF 格式的文件相比,RDB 文件可以更快的重启。

2)RDB 缺点

  • 因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失一段时间内的 Redis 数据;
  • RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork() 可能很耗时,并且如果数据集很大且 CPU 性能不佳,则可能导致 Redis 停止为客户端服务几毫秒甚至一秒钟。

三、Redis 持久化AOF

AOF(Append Only File)中文是附加到文件,顾名思义 AOF 可以把 Redis 每个键值对操作都记录到文件(appendonly.aof)中。

使用 RDB 持久化有一个风险,它可能会造成最新数据丢失的风险。因为 RDB 的持久化有一定的时间间隔,在这个时间段内如果 Redis 服务意外终止的话,就会造成最新的数据全部丢失。可能会操作 Redis 服务意外终止的条件:

  • 安装 Redis 的机器停止运行,蓝屏或者系统崩溃;
  • 安装 Redis 的机器出现电源故障,例如突然断电;
  • 使用 ​​kill -9 Redis_PID​​ 等。

3.1 持久化查询和设置

使用 ​​config get appendonly​​ 命令,如下图所示:其中,第一行为 AOF 文件的名称,而最后一行表示 AOF 启动的状态,yes 表示已启动,no 表示未启动。

Redis——持久化(RDB与AOF)原理

开启 AOF 持久化:Redis 默认是关闭 AOF 持久化的,想要开启 AOF 持久化,有以下两种方式:

  • 通过命令行的方式;
  • 通过修改配置文件的方式(redis.conf)。

命令行启动 AOF,使用 ​​config set appendonly yes​​ 命令,命令行启动 AOF 的优缺点:命令行启动优点是无需重启 Redis 服务,缺点是如果 Redis 服务重启,则之前使用命令行设置的配置就会失效。如下图所示:

Redis——持久化(RDB与AOF)原理

配置文件启动 AOF:Redis 的配置文件在它的根路径下的 redis.conf 文件中,获取 Redis 的根目录可以使用命令 ​​config get dir​​​ 获取,只需要在配置文件中设置 ​​appendonly yes​​​ 即可,默认 ​​appendonly no​​ 表示关闭 AOF 持久化。 配置文件启动 AOF 的优缺点:修改配置文件的缺点是每次修改配置文件都要重启 Redis 服务才能生效,优点是无论重启多少次 Redis 服务,配置文件中设置的配置信息都不会失效。如下图所示:

Redis——持久化(RDB与AOF)原理

3.2 触发持久化

AOF 持久化开启之后,只要满足一定条件,就会触发 AOF 持久化。AOF 的触发条件分为两种:自动触发和手动触发。

自动触发:有两种情况可以自动触发 AOF 持久化,分为是:满足 AOF 设置的策略触发和**满足 AOF 重写触发。**其中,AOF 重写触发会在本文的后半部分详细介绍,这里重点来说 AOF 持久化策略都有哪些。 AOF 持久化策略,分为以下三种:

  • always:每条 Redis 操作命令都会写入磁盘,最多丢失一条数据;
  • everysec:每秒钟写入一次磁盘,最多丢失一秒的数据;
  • no:不设置写入磁盘的规则,根据当前操作系统来决定何时写入磁盘,Linux 默认 30s 写入一次数据至磁盘。
这三种配置可以在 Redis 的配置文件(redis.conf)中设置,如下代码所示:

# 开启每秒写入一次的持久化策略
appendfsync everysec

小贴士:因为每次写入磁盘都会对 Redis 的性能造成一定的影响,所以要根据用户的实际情况设置相应的策略,一般设置每秒写入一次磁盘的频率就可以满足大部分的使用场景了。

Redis——持久化(RDB与AOF)原理

手动触发,在客户端执行 ​​bgrewriteaof​​​ 命令就可以手动触发 AOF 持久化,可以看出执行完 ​​bgrewriteaof​​ 命令之后,AOF 持久化就会被触发。如下图所示:

Redis——持久化(RDB与AOF)原理

3.3 AOF 文件重写

AOF 是通过记录 Redis 的执行命令来持久化(保存)数据的,所以随着时间的流逝 AOF 文件会越来越多,这样不仅增加了服务器的存储压力,也会造成 Redis 重启速度变慢,为了解决这个问题 Redis 提供了 AOF 重写的功能。

什么是 AOF 重写:AOF 重写指的是它会直接读取 Redis 服务器当前的状态,并压缩保存为 AOF 文件。例如,我们增加了一个计数器,并对它做了 99 次修改,如果不做 AOF 重写的话,那么持久化文件中就会有 100 条记录执行命令的信息,而 AOF 重写之后,之后记录一条此计数器最终的结果信息,这样就去除了所有的无效信息。

AOF 重写实现:触发 AOF 文件重写,要满足两个条件,这两个条件也是配置在 Redis 配置文件中的,它们分别:

  • auto-aof-rewrite-min-size:允许AOF重写的最小文件容量,默认是 64mb。
  • auto-aof-rewrite-percentage:AOF 文件重写的大小比例,默认值是 100,表示 100%,也就是只有当前 AOF 文件,比最后一次(上次)的 AOF 文件大一倍时,才会启动 AOF 文件重写。

查询 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 的值,可使用 ​​config get xxx​​ 命令,如下图所示:

Redis——持久化(RDB与AOF)原理

小贴士:只有同时满足 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 设置的条件,才会触发 AOF 文件重写。注意:使用 ​​bgrewriteaof​​ 命令,可以自动触发 AOF 文件重写。

AOF 文件重写是生成一个全新的文件,并把当前数据的最少操作命令保存到新文件上,当把所有的数据都保存至新文件之后,Redis 会交换两个文件,并把最新的持久化操作命令追加到新文件上。

3.3.1 AOF文件重写实现原理

Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但 新AOF文件 不会包含 任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。首先 从数据库中 读取 键现在的值,然后 用一条命令 去记录 键值对,代替 之前记录这个键值对的多条命令,这就是AOF重写功能的实现原理。

public void aof_rewrite(String new_aof_file_name):
# 创建新 AOF 文件
f = create_file(new_aof_file_name)
# 遍历数据库
for db in redisServer.db:
# 忽略空数据库
if db.is_empty(): continue
# 写入SELECT命令,指定数据库号码
f.write_command("SELECT" + db.id)
# 遍历数据库中的所有键
for key in db:
# 忽略已过期的键
if key.is_expired(): continue
# 根据键的类型对键进行重写
if key.type == String:
rewrite_string(key)
elif key.type == List:
rewrite_list(key)
elif key.type == Hash:
rewrite_hash(key)
elif key.type == Set:
rewrite_set(key)
elif key.type == SortedSet:
rewrite_sorted_set(key)
# 如果键带有过期时间,那么过期时间也要被重写
if key.have_expire_time():
rewrite_expire_time(key)
# 写入完毕,关闭文件
f.close()



public void rewrite_string(String key):
# 使用GET命令获取字符串键的值
value = GET(key)
# 使用SET命令重写字符串键
f.write_command(SET, key, value)



public void rewrite_list(String key):
# 使用LRANGE命令获取列表键包含的所有元素
item1, item2, ..., itemN = LRANGE(key, 0, -1)
# 使用RPUSH命令重写列表键
f.write_command(RPUSH, key, item1, item2, ..., itemN)



public void rewrite_hash(String key):
# 使用HGETALL命令获取哈希键包含的所有键值对
field1, value1, field2, value2, ..., fieldN, valueN = HGETALL(key)
# 使用HMSET命令重写哈希键
f.write_command(HMSET, key, field1, value1, field2, value2, ..., fieldN, valueN)



public void rewrite_set(String key);
# 使用SMEMBERS命令获取集合键包含的所有元素
elem1, elem2, ..., elemN = SMEMBERS(key)
# 使用SADD命令重写集合键
f.write_command(SADD, key, elem1, elem2, ..., elemN)



public void rewrite_sorted_set(String key):
# 使用ZRANGE命令获取有序集合键包含的所有元素
member1, score1, member2, score2, ..., memberN, scoreN = ZRANGE(key, 0, -1, "WITHSCORES")
# 使用ZADD命令重写有序集合键
f.write_command(ZADD, key, score1, member1, score2, member2, ..., scoreN, memberN)



public void rewrite_expire_time(String key):
# 获取毫秒精度的键过期时间戳
timestamp = get_expire_time_in_unixstamp(key)
# 使用PEXPIREAT命令重写键的过期时间
f.write_command(PEXPIREAT, key, timestamp)

因为​​aof_rewrite​​​函数生成的新AOF文件 只包含 还原当前数据库状态所必须的命令,所以新AOF文件不会浪费任何硬盘空间。注意:在实际中,为了避免 在执行命令时 造成 客户端输入缓冲区溢出,重写程序 在处理 列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,如果元素的数量超过了redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD常量的值,那么 重写程序 将使用 多条命令来记录键的值,而不单单使用一条命令。在目前版本中,​​REDIS_AOF_REWRITE_ITEMS_PER_CMD​​​常量的值为64,这也就是说,如果一个集合键包含了超过64个元素,那么重写程序会用多条​​SADD​​命令来记录这个集合,并且每条命令设置的元素数量也为64个:

SADD <set-key> <elem1> <elem2> ... <elem64>
SADD <set-key> <elem65> <elem66> ... <elem128>
SADD <set-key> <elem129> <elem130> ... <elem192>

如果一个列表键包含了超过64个项,那么重写程序会用多条​​RPUSH​​命令来保存这个列表,并且每条命令设置的项数量也为64个:

RPUSH <list-key> <item1> <item2> ... <item64>
RPUSH <list-key> <item65> <item66> ... <item128>
RPUSH <list-key> <item129> <item130> ... <item192>

重写程序使用类似的方法处理包含多个元素的有序集合键,以及包含多个键值对的哈希表键。

3.3.2 BGREWRITEAOF命令实现原理

​aof_rewrite​​​函数可以很好地完成创建一个新AOF文件的任务,但是,因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞。因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器直接调用​​aof_rewrite​​函数的话,那么在重写AOF文件期间,服务期将无法处理客户端发来的命令请求。

Redis 决定 将AOF重写程序(aof_rewrite) 放到 子进程里执行,这样做可以同时达到两个目的:

  •     子进程 进行 AOF重写 期间,服务器进程(父进程)可以继续处理命令请求。
  •     子进程 带有 服务器进程 的 数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

不过,使用子进程也有一个问题需要解决,因为子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的AOF文件所保存的数据库状态不一致。

下面展示了一个AOF文件重写例子,当子进程开始进行文件重写时,数据库中只有k1一个键,但是当子进程完成AOF文件重写之后,服务器进程的数据库中已经新设置了k2、k3、k4三个键,因此,重写后的AOF文件和服务器当前的数据库状态并不一致,新的AOF文件只保存了k1一个键的数据,而服务器数据库现在却有k1、k2、k3、k4四个键

时间

服务器进程

子进程

T1

执行命令set k1 v1

T2

执行命令set k1 v2

T3

执行命令set k1 v3

T4

创建子进程,执行AOF文件重写

开始AOF文件重写

T5

执行命令set k2 100

执行重写操作

T6

执行命令set k3 101

执行重写操作

T7

执行命令set k4 102

完成AOF的重写操作

为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区 在服务器 创建子进程之后 开始使用,当Redis服务器 执行完一个 写命令之后,它会 同时 将这个写命令 发送给 AOF缓冲区 和 AOF重写缓冲区。

Redis——持久化(RDB与AOF)原理

服务器进程需要执行以下三个工作:

  1. 执行客户端发来的命令
  2. 将执行后的写命令 追加到 AOF缓冲区
  3. 将执行后的写命令 追加到 AOF重写缓冲区

这样一来可以保证:

AOF缓冲区的内容 会定期 被写入 和 同步到AOF文件(通过appendfsync选项),对现有AOF文件的处理工作会如常进行。

从创建子进程开始,服务器执行的所有写命令 都会被 记录到 AOF重写缓冲区 里面(这句话前后半句是两件事,互不干扰)

当子进程完成 AOF重写工作 之后,它会向父进程发送一个信号,父进程在接到该信号之后,会调用一个信号处理函数,并执行以下工作:

  • 将 AOF重写缓冲区中的所有内容 写入到 新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
  • 对新的AOF文件进行改名,原子地(atomic)覆盖现有的AOF文件,完成新旧两个AOF文件的替

这个信号处理函数执行完毕之后,父进程就可以继续像往常一样接受命令请求了。

3.3.3 AOF文件重写流程

Redis——持久化(RDB与AOF)原理

  1. 执行AOF重写请求
ERR Background append only file rewriting already in progress

如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成之后再执行,返回如下响应:

Background append only file rewriting scheduled
  1. 父进程执行fork创建子进程,开销等同于bgsave过程。
  2. 主进程fork操作完成后,继续响应其他命令。所有修改命令依然写入AOF缓冲区 并根据 appendfsync策略同步到硬盘,保证原有AOF机制正确性。
  3. 由于fork操作运用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。
  4. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。每次批量写入硬盘数据量由配置aof-rewrite-incremental-fsync控制,默认为32MB,防止单次刷盘数据过多造成硬盘阻塞
  5. 新AOF文件写入完成后,子进程发送信号给父进程,父进程更新统计信息。
  6. 父进程把AOF重写缓冲区的数据写入到新的AOF文件。
  7. 使用新AOF文件替换老文件,完成AOF重写。

3.4 AOF配置说明

合理的设置 AOF 的配置,可以保障 Redis 高效且稳定的运行,以下是 AOF 的全部配置信息和说明。AOF 的配置参数在 Redis 的配置文件中,也就是 Redis 根路径下的 ​​redis.conf​​ 文件中,其中比较重要的是 appendfsync 参数,用它来设置 AOF 的持久化策略,可以选择按时间间隔或者操作次数来存储 AOF 文件,配置参数和说明如下:

# 是否开启 AOF,yes 为开启,默认是关闭
appendonly no

# AOF 默认文件名
appendfilename "appendonly.aof"

# AOF 持久化策略配置
# appendfsync always
appendfsync everysec
# appendfsync no

# AOF 文件重写的大小比例,默认值是 100,表示 100%,也就是只有当前 AOF 文件,比最后一次的 AOF 文件大一倍时,才会启动 AOF 文件重写。
auto-aof-rewrite-percentage 100

# 允许 AOF 重写的最小文件容量
auto-aof-rewrite-min-size 64mb

# 是否开启启动时加载 AOF 文件效验,默认值是 yes,表示尽可能的加载 AOF 文件,忽略错误部分信息,并启动 Redis 服务。
# 如果值为 no,则表示,停止启动 Redis,用户必须手动修复 AOF 文件才能正常启动 Redis 服务。
aof-load-truncated yes

3.5 AOF 下的数据恢复

3.5.1 数据的恢复的原理:

加载损坏的AOF文件时会拒绝启动,并打印如下日志:

# Bad file format reading the append only file: make a backup of your AOF file, 

then use ./redis-check-aof --fix <filename>

对于错误格式的AOF文件,先进行备份,然后采用​​redis-check-aof--fix​​​命令进行修复,修复后使用​​diff -u​​对比数据的差异,找出丢失的数据,有些可以人工修改补全。

AOF文件可能存在结尾不完整的情况,比如机器突然掉电 导致AOF尾部文件命令写入不全,Redis为我们提供了​​aof-load-truncated​​配置来兼容这种情况,默认开启。加载AOF时,当遇到此问题时会忽略并继续启动,同时打印如下警告日志:

# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 397856725 !!!
# AOF loaded anyway because aof-load-truncated is enabled

正常数据恢复,正常情况下,只要开启了 AOF 持久化,并且提供了正常的 appendonly.aof 文件,在 Redis 启动时就会自定加载 AOF 文件并启动,小贴士:默认情况下 appendonly.aof 文件保存在 Redis 的根目录下。执行如下图所示:

Redis——持久化(RDB与AOF)原理

持久化文件加载规则

  • 如果只开启了 AOF 持久化,Redis 启动时只会加载 AOF 文件(appendonly.aof),进行数据恢复;
  • 如果只开启了 RDB 持久化,Redis 启动时只会加载 RDB 文件(dump.rdb),进行数据恢复;
  • 如果同时开启了 RDB 和 AOF 持久化,Redis 启动时只会加载 AOF 文件(appendonly.aof),进行数据恢复。

在 AOF 开启的情况下,即使 AOF 文件不存在,只有 RDB 文件,也不会加载 RDB 文件。 AOF 和 RDB 的加载流程如下图所示: 

Redis——持久化(RDB与AOF)原理

简单异常数据恢复:在 AOF 写入文件时如果服务器崩溃,或者是 AOF 存储已满的情况下,AOF 的最后一条命令可能被截断,这就是异常的 AOF 文件。在 AOF 文件异常的情况下,如果为修改 Redis 的配置文件,也就是使用 ​​aof-load-truncated​​​ 等于 ​​yes​​ 的配置,Redis 在启动时会忽略最后一条命令,并顺利启动 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

复杂异常数据恢复:AOF 文件可能出现更糟糕的情况,当 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>

出现此类问题的解决方案如下:

  1. 首先使用 AOF 修复工具,检测出现的问题,在命令行中输入 ​​redis-check-aof​​ 命令,它会跳转到出现问题的命令行,这个时候可以尝试手动修复此文件;
  2. 如果无法手动修复,我们可以使用 ​​redis-check-aof --fix​​ 自动修复 AOF 异常文件,不过执行此命令,可能会导致异常部分至文件末尾的数据全部被丢弃。

3.6 AOF的优缺点

AOF 优点

  • AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存一次、跟随系统的持久化策略保存,其中每秒保存一次,从数据的安全性和性能两方面考虑是一个不错的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据;
  • AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写入了一半,也可以通过 redis-check-aof 工具轻松的修复;
  • AOF 持久化文件,非常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了磁盘。即使不小心使用 ​​flushall​​​ 命令删除了所有键值信息,只要使用 AOF 文件,删除最后的 ​​flushall​​ 命令,重启 Redis 即可恢复之前误删的数据。

AOF 缺点

  • 对于相同的数据集来说,AOF 文件要大于 RDB 文件;
  • 在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好;
  • RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执行的命令追加到 AOF 文件中,因此从理论上说,RDB 比 AOF 更健壮。

AOF 保存数据更加完整,它可以记录每次 Redis 的键值变化,或者是选择每秒保存一次数据。AOF 的持久化文件更加易读,但相比与二进制的 RDB 来说,所占的存储空间也越大,为了解决这个问题,AOF 提供自动化重写机制,最大程度的减少了 AOF 占用空间大的问题。同时 AOF 也提供了很方便的异常文件恢复命令: ​​redis-check-aof --fix​​ ,为使用 AOF 提供了很好的保障。

四、混合持久化

RDB 和 AOF 持久化各有利弊,RDB 可能会导致一定时间内的数据丢失,而 AOF 由于文件较大则会影响 Redis 的启动速度,为了能同时使用 RDB 和 AOF 各种的优点,Redis 4.0 之后新增了混合持久化的方式。

在开启混合持久化的情况下,AOF 重写时会把 Redis 的持久化数据,以 RDB 的格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式化追加的文件的末尾。

Redis——持久化(RDB与AOF)原理

 4.1 开启混合持久化

查询是否开启混合持久化可以使用 ​​config get aof-use-rdb-preamble​​ 命令,执行结果如下图所示:

Redis——持久化(RDB与AOF)原理

其中 yes 表示已经开启混合持久化,no 表示关闭,Redis 5.0 默认值为 yes。 如果是其他版本的 Redis 首先需要检查一下,是否已经开启了混合持久化,如果关闭的情况下,可以通过以下两种方式开启:

  • 通过命令行开启
  • 通过修改 Redis 配置文件开启

使用命令 ​​config set aof-use-rdb-preamble yes​​ 执行结果如下图所示:小贴士:命令行设置配置的缺点是重启 Redis 服务之后,设置的配置就会失效。

Redis——持久化(RDB与AOF)原理

在 Redis 的根路径下找到 redis.conf 文件,把配置文件中的 ​​aof-use-rdb-preamble no​​​ 改为 ​​aof-use-rdb-preamble yes​​ 如下图所示:

Redis——持久化(RDB与AOF)原理

当在混合持久化关闭的情况下,使用 ​​bgrewriteaof​​ 触发 AOF 文件重写之后,查看 appendonly.aof 文件的持久化日志,如下图所示:

Redis——持久化(RDB与AOF)原理

可以看出,当混合持久化关闭的情况下 AOF 持久化文件存储的为标准的 AOF 格式的文件。 当混合持久化开启的模式下,使用 ​​bgrewriteaof​​ 命令触发 AOF 文件重写,得到 appendonly.aof 的文件内容如下图所示:

Redis——持久化(RDB与AOF)原理

 可以看出 appendonly.aof 文件存储的内容是 ​​REDIS​​ 开头的 RDB 格式的内容,并非为 AOF 格式的日志。

4.2 数据恢复

混合持久化的数据恢复和 AOF 持久化过程是一样的,只需要把 appendonly.aof 放到 Redis 的根目录,在 Redis 启动时,只要开启了 AOF 持久化,Redis 就会自动加载并恢复数据。 Redis 启动信息如下图所示:

Redis——持久化(RDB与AOF)原理

可以看出 Redis 在服务器初始化的时候加载了 AOF 文件的内容。

4.3 混合持久化的加载流程

混合持久化的加载流程如下:

  1. 判断是否开启 AOF 持久化,开启继续执行后续流程,未开启执行加载 RDB 文件的流程;
  2. 判断 appendonly.aof 文件是否存在,文件存在则执行后续流程;
  3. 判断 AOF 文件开头是 RDB 的格式, 先加载 RDB 内容再加载剩余的 AOF 内容;
  4. 判断 AOF 文件开头不是 RDB 的格式,直接以 AOF 格式加载整个文件。

Redis——持久化(RDB与AOF)原理

4.4 源码解析

Redis 判断 AOF 文件的开头是否是 RDB 格式的,是通过关键字 ​​REDIS​​​ 判断的,RDB 文件的开头一定是 ​​REDIS​​ 关键字开头的,判断源码在 Redis 的 src/aof.c 中,核心代码如下所示:

char sig[5]; /* "REDIS" */
if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
// AOF 文件开头非 RDB 格式,非混合持久化文件
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
} else {
/* RDB preamble. Pass loading the RDB functions. */
rio rdb;

serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp);
// AOF 文件开头是 RDB 格式,先加载 RDB 再加载 AOF
if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
goto readerr;
} else {
serverLog(LL_NOTICE,"Reading the remaining AOF tail...");
}
}
// 加载 AOF 格式的数据

可以看出 Redis 是通过判断 AOF 文件的开头是否是 ​​REDIS​​ 关键字,来确定此文件是否为混合持久化文件的。小贴士:AOF 格式的开头是 *,而 RDB 格式的开头是 REDIS。

4.5 混合持久化的优缺点

混合持久化优点:

  • 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了大量数据丢失的风险。

混合持久化缺点:

  • AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
  • 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能用在 Redis 4.0 之前版本了。

持久化虽然保证了数据不丢失,但同时拖慢了 Redis 的运行速度,那怎么更合理的使用 Redis 的持久化功能呢? Redis 持久化的最佳实践可从以下几个方面考虑。

1)控制持久化开关

使用者可根据实际的业务情况考虑,如果对数据的丢失不敏感的情况下,可考虑关闭 Redis 的持久化,这样所以的键值操作都在内存中,就可以保证最高效率的运行 Redis 了。 持久化关闭操作:

  • 关闭 RDB 持久化,使用命令: ​​config set save ""​
  • 关闭 AOF 和 混合持久化,使用命令: ​​config set appendonly no​

2)主从部署

使用主从部署,一台用于响应主业务,一台用于数据持久化,这样就可能让 Redis 更加高效的运行。

3)使用混合持久化

混合持久化结合了 RDB 和 AOF 的优点,Redis 5.0 默认是开启的。

4)使用配置更高的机器

Redis 对 CPU 的要求并不高,反而是对内存和磁盘的要求很高,因为 Redis 大部分时候都在做读写操作,使用更多的内存和更快的磁盘,对 Redis 性能的提高非常有帮助。

五、Redis持久化的面试问题

重写后的AOF文件为什么可以变小?

  • 进程内已经超时的数据不再写入文件
  • 旧的AOF文件含有无效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令
  • 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等类型操作,以64个元素为界拆分为多条。