Redis稳定性之战:AOF日志支撑数据持久化

Redis24篇集合

1 介绍

AOF(Append Only File)持久化:以独立日志的方式存储了 Redis 服务器的顺序指令序列,并只记录对内存进行修改的指令。
当Redis服务发生雪崩等故障时,可以重启服务并重新执行AOF文件中的指令达到恢复数据的目的。也就是说,通过重放(replay),来重新建立 Redis 当前实例的内存数据结构。这种模式有没有很熟悉,可以联想到MySQL主从同步时的relay log。
相对于咱们上一篇介绍的《RDB内存快照提供持久化能力》定点快照的做法,AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

2 AOF实现日志记录

2.1 开启AOF日志记录

1、 开启AOF日志记录:在redis.conf文件中,找到 APPEND ONLY MODE 设置

appendonly yes  # 默认不开启, 为 no

2、配置默认文件名:在redis.conf文件中设置

appendfilename “appendonly.aof”

2.2 执行流程

image

流程如上图所示,我们解析如下:

2.2.1 将所有的写命令(set、hset)Append 到aof_buf缓冲区中

Redis 接收到 set keyName someValue 命令的时候,会先将数据写到内存,Redis 会按照如下格式写入 AOF 文件。

  1. *3:表示当前指令分为三个部分,每个部分都是 $ + 数字开头,后面是3部分的具体内容:指令、键、值。
  2. 数字:表示这部分的命令、键、值多占用的字节大小。比如 $3表示这部分包含 3 个字符,也就是 set 的长度。

我们看看一个典型的aof文件示例,为了清晰表示,下面的注释都是手动加的:

[root@localhost bin]#vim appendonly.aof
#  执行 set key value
*3
$3           # 这边代表set命令,长度为3
set
$9 
user_name      # 这边代表keyName,长度为9
$5 
brand      #  这边代表keyValue,长度为5


# 执行 mset key1 1 ,key2 2 ,key33 3
# aof日志如下:
*7  # 本批命令需要往下读7行非 $ 开始的命令
$4  #接着读取4个字节宽度,‘mset’长度为4,记为 $4
mset
$4  #接着读取4个字节宽度,‘key1’长度为4,记为 $4
key1
$1  #接着读取1个字节宽度,‘1’长度为1,记为 $1
1
$4
key2
$1
2
$5  #接着读取的字节宽度,‘$key33’长度为5,记为 $5
key33
$1
3

2.2.2 AOF缓冲区根据策略向硬盘做sync同步

AOF为什么把命令append到aof_buf中,然后再进行同步?
这是因为Redis使用单进程响应命令(参考笔者这篇《深刻理解高性能Redis的本质》),如果每次写AOF文件命令都直接持久化到硬盘,那么操作会是不是被间断,且性能完全取决于硬盘I/O负载。这个跟 MySQL 就没啥区别了。
先写入缓冲区aof_buf中,Redis可以提供多种缓冲区同步硬盘的策略,在性能、安全、数据可靠性方面做出平衡。

同步策略需关注以下几个配置:

1、 appendfsync 模式

appendfsync always  # 接受写命令后立即写入磁盘,强持久化但执行慢,不推荐
appendfsync everysec # 每秒写入磁盘一次, 性能和持久化方面做了折中, 推荐
appendfsync no  #  依赖操作系统自身同步的配置和策略,性能较佳,但是没法保证实时和完全持久化

2、no-appendfsync-on-rewrite
在 AOF 重写期间是否禁用 fsync。这可以提高重写性能,但可能会增加数据丢失的风险。

# 默认值:no
# 可选值:yes 或 no
no-appendfsync-on-rewrite yes

2.2.3 AOF文件Rewrite实现压缩

随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩减负的目的,避免AOF文件过大导致性能和数据可靠性问题。
重写后的AOF文件变小的原因主要有以下几点:
1、进程内已超时的数据不再写入:在重写过程中,Redis不会将已经超时的数据写入新的AOF文件,这有助于减少不必要的数据记录。
2、删除无效命令:旧的AOF文件中可能包含无效的命令,如del key1hdel key2srem keysset a111等。重写过程会识别并删除这些无效命令,只保留最终数据的写入命令,从而减小了文件大小。
3、合并多条写命令:为了进一步优化AOF文件的大小,重写过程会将多条写命令合并为一个。例如,lpush list alpush list blpush list c可以合并为lpush list a b c。这种合并减少了命令的数量,进而减小了AOF文件的大小。
4、防止单条命令过大:对于某些操作类型(如list、set、hash、zset),为了防止单条命令过大造成客户端缓冲区溢出,重写过程会以64个元素为界拆分多条命令。虽然这在一定程度上可能增加了命令的数量,但它确保了每条命令的大小都在可控范围内,有助于维持整体文件大小的合理性。
总之AOF重写降低了文件占用空间,同时提升加载性能,因为更小的AOF 文件可以更快地被Redis加载。

AOF重写关注以下配置:
1、auto-aof-rewrite-percentage
触发 AOF 重写的增长百分比。例如,如果当前 AOF 文件大小是 100MB,并且这个值设置为 100,那么当 AOF 文件增长到 200MB 时,说明增长了100%,Redis 会尝试重写 AOF。

# 默认值:`100`
`auto-aof-rewrite-percentage 100`

2、auto-aof-rewrite-min-size

AOF 文件的最小大小,以便触发重写。即使 AOF 文件的增长百分比超过了 auto-aof-rewrite-percentage 设置的值,但如果文件大小小于这个值,Redis 也不会触发重写。

# 默认值:`64mb`
auto-aof-rewrite-min-size 64mb

image

2.2.4 故障重启时的数据恢复

当Redis服务器重启时,可以加载AOF文件进行数据恢复。
image

流程如下:

  1. 当AOF和RDB文件同时存在时,优先加载AOF
  2. 若关闭了AOF(apendonly no),则加载RDB文件
  3. 加载AOF/RDB成功之后,redis重启成功。如果无相关的持久化,则直接启动成功。
  4. 如果AOF/RDB 数据恢复存在错误,则启动失败,并打印输出错误信息

2.3 RDB和AOF的比较和混合持久化

咱们上一篇介绍了《RDB内存快照提供持久化能力》定点快照的用户,那RDB跟AOF究竟孰优孰虑?
现实情况下,无论使用RDB或者AOF都差点意思。使用 rdb 来恢复内存状态,势必会丢失一部分数据。使用 AOF 日志重放,重放对性能有一定的影响,而且在 Redis 实例很大的情况下,需要花费很长的时间。
Redis 4.0 解决了这个问题,才用了一个新的持久化模式——混合持久化,该 混合模式 默认是关闭状态的。
将 RDB 文件的内容和 rdb快照时间点之后的增量的 AOF 日志文件存在一起。这时候 AOF 日志不需要再是全量的日志,而是最近一次快照时间点之后到当下发生的增量 AOF 日志,通常这部分 AOF 日志很小。
所以执行有如下顺序:

  • 查找rdb内容,如果存在先加载 rdb内容再 重放剩余的 aof。
  • 没有rdb内容,直接以aof格式重放整个文件。
    这样快照就不用频繁的执行,同时由于 AOF 只需要记录最近一次快照之后的数据,不需要记录所有的操作,避免了出现单次重放文件过大的问题。

开启混合持久化模式:

aof-use-rdb-preamble yes

这个设置告诉Redis在AOF重写时使用混合持久化模式。当这个选项设置为yes时,重写后的AOF文件将包含RDB格式的数据前缀和AOF格式的增量修改操作。

总结

  • RDB提供了快照模式,记录某个时间的Redis内存状态。RDB设计了 bgsave 和写时复制,尽可能避免执行快照期间对读写指令的影响,但是频繁快照会给磁盘带来压力以及 fork 阻塞主线程。需把握频率。
  • AOF 日志存储了 Redis 服务的顺序指令序列,通过重放(replay)指令来写入日志文件,并通过写回策略来避免高频读写给Redis带来压力。
  • RDB快照的照片时间间隔,必然会带来数据缺失,如果允许分钟级别的数据丢失,可以只使用 RDB。
  • 如果只用 AOF,写回策略优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。
  • 数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择。
posted @ 2024-03-12 09:30  Hello-Brand  阅读(428)  评论(0编辑  收藏  举报