Redis稳定性之战:AOF日志支撑数据持久化
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 执行流程
流程如上图所示,我们解析如下:
2.2.1 将所有的写命令(set、hset)Append 到aof_buf缓冲区中
Redis 接收到 set keyName someValue
命令的时候,会先将数据写到内存,Redis 会按照如下格式写入 AOF 文件。
*3
:表示当前指令分为三个部分,每个部分都是$ +
数字开头,后面是3部分的具体内容:指令、键、值。- 数字:表示这部分的命令、键、值多占用的字节大小。比如
$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 key1
、hdel key2
、srem keys
、set a111
等。重写过程会识别并删除这些无效命令,只保留最终数据的写入命令,从而减小了文件大小。
3、合并多条写命令:为了进一步优化AOF文件的大小,重写过程会将多条写命令合并为一个。例如,lpush list a
、lpush list b
、lpush 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
2.2.4 故障重启时的数据恢复
当Redis服务器重启时,可以加载AOF文件进行数据恢复。
流程如下:
- 当AOF和RDB文件同时存在时,优先加载AOF
- 若关闭了AOF(apendonly no),则加载RDB文件
- 加载AOF/RDB成功之后,redis重启成功。如果无相关的持久化,则直接启动成功。
- 如果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 的混合使用是一个很好的选择。