AOF
AOF(Append Only File)持久化:以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF解决了数据持久化的实时性
问题
使用 AOF
- 开启 AOF 功能需要设置配置:
appendonly yes
,默认不开启 - AOF 文件名通过
appendfilename
配置(默认是appendonly.aof
)设置。 - 保存目录同 RDB 持久化方式一致,通过
dir
配置指定 - AOF 的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如图所示。
redis.conf 配置示例
- 启用 AOF 持久化
appendonly yes- 设置 AOF 文件名(默认即为 appendonly.aof,可根据需要修改)
appendfilename “appendonly.aof”- 指定 AOF 文件保存目录(与 RDB 文件共享此配置)
dir /var/lib/redis/data # 示实际使用时根据环境调整将上述配置添加到或修改 Redis 配置文件(通常是 redis.conf)后,重启 Redis 服务即可生效
工作流程
- 所有的写入命令会追加到
aof_buf
(缓冲区)中 - AOF 缓冲区根据对应的策略对硬盘做同步操作
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的
- 当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复
命令写入
AOF 命令写入的内容直接是文本协议格式。例如 set hello world
这条命令,在 AOF 缓冲区会追加如下文本:
1 *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
此处遵守 Redis 格式协议,Redis 选择文本协议的可能原因:文本协议具备较好的兼容性;实现简单;具备可读性
AOF 是每次把新的操作写入到原有文件的末尾,属于顺序写入
aof_buf 缓冲区
缓存区还是在内存中的,如果没有及时写入硬盘,数据还是会丢失的
AOF 过程中为什么需要 aof_buf
这个缓冲区?
Redis 使用单线程响应命令,如果每次写 AOF 文件都直接同步硬盘,性能从内存的读写变成 IO 读写,速度会下降。先写入缓冲区可以有效减少 IO 次数,同时,Redis 还可以提供多种缓冲区同步策略
文件同步
Redis 提供了多种 AOF 缓冲区同步文件策略,由参数 appendfsync
控制,不同值的含义如表所示。
AOF 缓冲区同步文件策略表
可配置值 | 说明 |
---|---|
always | 命令写入 aof_buf 后调用 fsync 同步,完成后返回 |
everysec | 命令写入 aof_buf 后只执行 write 操作,不进行 fsync 。每秒由同步线程进行 fsync 。 |
no | 命令写入 aof_buf 后只执行 write 操作,由 OS 控制 |
系统调用 write
和 fsync
说明:
write
操作会触发延迟写(delayed write)机制。Linux 在内核提供页缓冲区用来提供硬盘 IO 性能。write
操作在写入系统缓冲区后立即返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。fsync
针对单个文件操作,做强制硬盘同步,fsync
将阻塞直到数据写入到硬盘。- 配置为
always
时,每次写入都要同步 AOF 文件,性能很差,在一般的 SATA 硬盘上,只能支持大约几百 TPS 写入 - 配置为
no
时,由于操作系统同步策略不可控,虽然提高了性能,但数据丢失风险大增 - 配置为
everysec
,是默认配置,也是推荐配置,兼顾了数据安全性和性能。理论上最多丢失 1 秒的数据。
重写机制
随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis 引入 AOF 重写机制压缩文件体积。AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF 文件。
重写后的 AOF 为什么可以变小
- 进程内已超时的数据不再写入文件。
- 旧的 AOF 中的无效命令,例如
del
、hdel
、srem
等重写后将会删除,只需要保留数据的最终版本。 - 多条写操作合并为一条,例如
lpush list a
、lpush list b
、lpush list c
可以合并为lpush list a b c
。
较小的 AOF 文件一方面降低了硬盘空间占用,一方面可以提升启动 Redis 时数据恢复的速度。
AOF 重写过程可以手动触发和自动触发:
- 手动触发:调用
bgrewriteaof
命令。 - 自动触发:根据
auto-aof-rewrite-min-size
和auto-aof-rewrite-percentage
参数确定自动触发时机。auto-aof-rewrite-min-size
:表示触发重写时 AOF 的最小文件大小,默认为 64MB。auto-aof-rewrite-percentage
:代表当前 AOF 占用大小相比较上次重写时增加的比例。
如果在执行
bgrewriteaof
的时候,当前 redis 已经正在进行 aof 重写了此时,不会再次执行 aof 重写,直接返回了。
如果在执行
bgrewriteaof
的时候,发现当前 redis 在生成 rdb 文件的快照此时,aof 重写操作就会等待,等待 rdb 快照生成完毕之后,再进行执行 aof 重写
AOF 重写流程
- 执行 AOF 重写请求。
如果当前进程正在执行 AOF 重写,请求不执行。如果当前进程正在执行bgsave
操作,重写命令延迟到bgsave
完成之后再执行。 - 父进程执行
fork
创建子进程。 - 重写
a. 主进程fork
之后,继续响应其他命令。所有修改操作写入 AOF 缓冲区并根据appendfsync
策略同步到硬盘,保证旧 AOF 文件机制正确。
b. 子进程只有fork
之前的所有内存信息,父进程中需要将fork
之后这段时间的修改操作写入 AOF 重写缓冲区中。 - 子进程根据内存快照,将命令合并到新的 AOF 文件中。
- 子进程完成重写
a. 新文件写入后,子进程发送信号给父进程。
b. 父进程把 AOF 重写缓冲区内临时保存的命令追加到新 AOF 文件中。
c. 用新 AOF 文件替换老 AOF 文件。
父进程
fork
完毕之后,就已经让子进程写新的 aof 文件了,并且随着时间的推移,子进程很快就写完了新的文件,要让新的 aof 文件代替旧的。父进程此时还在继续写这个即将消亡的旧的 aof 文件是否还有意义?考虑到极端情况。假设在重写过程中,重写了一半了,服务器挂了,子进程内存的数据就会丢失,新的 aof 文件内容还不完整。所以如果父进程不坚持写旧日 aof 文件,重启就没法保证数据的完整性了。
启动时数据恢复
当 Redis 启动时,会根据 RDB 和 AOF 文件的内容,进行数据恢复。
混合持久化
AOF 本来是按照文本的方式来写入文件的。但是文本的方式写文件,后续加载的成本比较高。Redis 就引入了混合持久化的方式,结合了 RDB 和 AOF 的特点。
按照 AOF 的方式,每一个请求/操作,都记录入文件。在触发 AOF 重写之后,就会把当前内存的状态按照 RDB 的二进制格式写入到新的 AOF 文件:
aof-use-rdb-preamble yes
这个选项为yes
表示开启混合持久化
no
(修改配置项之后要记得重新启动服务器)
后续再进行的操作,仍然是按照 AOF 文本的方式追加到文件后面
总结
- Redis 提供了两种持久化方案:RDB 和 AOF。
- RDB 视为内存的快照,产生的内容更为紧凑,占用空间较小,恢复时速度更快。但产生 RDB 的开销较大,不适合进行实时持久化,一般用于冷备和主从复制。
- AOF 视为对修改命令保存,在恢复时需要重放命令。并且有重写机制来定期压缩 AOF 文件。
- RDB 和 AOF 都使用
fork
创建子进程,利用 Linux 子进程拥有父进程内存快照的特点进行持久化,尽可能不影响主进程继续处理后续命令。
RDB 本身的设计理念,就是用来“定期备份”的。只要是定期备份,就难以保证数据的实时性
AOF 的理念则是实时备份