「进击Redis」二十三、Redis 持久化之 AOF 原理解析

875 阅读8分钟

前言

好哥哥们,这一篇的话是上一篇超详细解析 Redis 持久化之 RDB 的姐妹篇,关于AOF持久化。Redis 一共就提供了这两种持久化的方式,虽然平时很少会有对 Redis 的运维工作(能有几个好哥哥在生产环境有对 Redis 做过高可用和容灾备份的),但是这玩意在面试的时候经常被问到。也是掌握 Redis 比较重要的一个技能点了,还是很有必要说一说的呢。

概述

AOF(append only file)持久化是以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。上篇关于RDB持久化中有提到就是RDB是不用用来做实时性或者秒级持久化的。那AOF的主要作用是解决了数据持久化的实时性,打开AOF后, 每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。这样的话, 当 Redis 重新启时, 程序就可以通过重新执行AOF 文件中的命令来达到重建数据集的目的。 AOF 持久化分成两个步骤:写入AOF 文件 和 异常数据恢复。

  1. 写入AOF 文件原理图:
    生成
  2. 数据恢复原理图:
    恢复

使用

AOF 持久化默认是不开启的,所以在使用之前需要需要设置配置(配置会贴在下面)。AOF文件名通过appendfilename配置设置,默认文件名是appendonly.aof。保存路径同RDB持久化方式一致,通过dir配置指定。AOF 的工作流程操作:命令写入(append)文件同步(sync)文件重写(rewrite)重启加载(load)

1 开启AOF

这个配置和文件名、目录默认都在redis.conf

appendonly no

2 命令追加

AOF 追加的命令采用的是文本协议格式,这个格式实际上我们在不得不知道的 Redis 通信协议 中有讲过。好哥哥们可以看下这篇,下面就举个简单的栗子,还是以set hello world 为例,实际存储的就是如下的字符串。

*3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n

3 文件同步策略

可以通过配置文件配置 Redis 多久才将数据 fsync ( fsync 是针对单个文件操作,比如AOF文件,做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回) 到磁盘一次。AOF 提供了三种方式alwaysnoeverysec

always

配置为 always 时,每次有新命令追加到 AOF 文件时就执行一次 fsync 。这种方式的话是三种策略中最慢的,也是最安全的。 always

no

从不执行fsync ,将数据交给操作系统来处理,由操作系统来决定什么时候同步数据。这种策略是最快的但是是最不安全的。 no

everysec

everysec 是默认的AOF策略,表示每秒中执行 fsync。这种策略是基于上面两种策略的一个中间策略,同时兼顾了性能和数据安全性。理论上来说故障时只会丢失 1 秒钟的数据。 sec

三种策略对比
命令优点缺点
always数据安全性最高I/O 开销大、性能最低、可能会阻塞服务器
no性能最好不可控、风险大、存在数据不一致性的问题
everysec兼顾性能和安全在某种情况下会丢失一秒钟的数据,也存在数据不一致性的问题

4 文件重写

这个实际上跟Mysql 中的查询语句优化是一样的,因为AOF采用的是追加策略,这样会导致一个问题就是AOF文件会越来越大。举个例子, 如果你对一个计数器调用了 100 次 INCR , 那么仅仅是为了保存这个计数器的当前值, 那这个时候 AOF 文件就需要记录 100 条INCR命令。然而在实际上, 只使用一条 SET 命令已经足以保存计数器的当前值了, 其余 99 条记录实际上都是多余的。

过程

当 Redis 执行AOF文件重写时,会异步创建一个当前AOF文件的体积优化版本。这个时候会出现两种情况,第一种就是执行重写时 Redis 没有接收到任何写入命令。第二种的话就是执行重写时 Redis 接收到了新的写操作命令(这个在注意点中会单独说)。不过即使 bgrewriteaof 执行失败,也不会有任何数据丢失,因为旧的 AOF文件在 bgrewriteaof 成功之前不会被修改。

重写

Redis 支持手动和自动触发对AOF 文件文件的重写

手动

直接在redis-cli调用bgrewriteaof命令,

自动

根据配置文件的auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage参数确定自动触发时机。

  1. auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB
  2. auto-aof-rewrite-percentage:代表当前AOF文件空间(aof_current_size)和上一次重写后 AOF 文件空间(aof_base_size)的比值
自动触发条件

其中aof_current_sizeaof_base_size 可以在 info Persistence 统计信息中查看(这个条件可能会有很多版本)。

  1. aof_current_size 大于 auto-aof-rewrite-min-size
  2. (aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage
重写内容
  1. 进程内已经超时的数据不再写入文件。
  2. 去除旧的AOF文件含有的无效命令,如del key1hdel key2srem keysseta111等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
  3. 将多条写命令可以合并为一个。如:lpush list alpush list blpush listc可以转化为:lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于listsethashzset等类型操作,以 64 个元素为界拆分为多条。
注意点

这里的话主要说一下,如果在执行AOF 重写时 Redis 接收到了新的写操作命令时出现的漏追加是怎么处理的。首先的话需要了解AOF 缓冲区AOF 重写缓冲区和两个概念。 AOF 缓冲区 AOF 缓冲区的主要的作用是当 Redis 开启了AOF持久化后,当 Redis 接收到写操作命令后,就会将命令同步放到这个缓冲区内,然后根据配置的刷盘策略(上面的那三种)执行fsync操作,然后清空。 AOF 重写缓冲区 AOF 重写缓冲区作用其实也是接收新的写操作命令,但是只会发生在AOF 重写期间。在这个期间内会一直接收新的写操作命令,知道重写完成后释放。 也就是说,当 Redis 执行AOF 文件重写时,会同时向AOF 缓冲区和重写缓冲区写入命令。往AOF 缓冲区写入命令的意义是旧的AOF 文件数据的一致性(根据配置也可能存在不一致),而AOF 重写缓冲区是保证优化过后新的AOF 文件的数据一致性,当子进程完成AOF重写后,它会向父进程发送一个信号,父进程收到信号后会调用一个信号处理函数,该函数把AOF重写缓冲区的命令追加到新AOF文件中然后替换掉现有AOF文件。
重写

5 重启加载

AOFRDB文件都可以用于服务器重启时的数据恢复。流程如下:

  1. AOF持久化开启且存在AOF文件时,Redis 会优先加载AOF文件。
  2. AOF关闭或者AOF文件不存在时,加载RDB文件。
  3. 加载AOF/RDB文件成功后,Redis 启动成功。
  4. AOF/RDB文件存在错误时,Redis 启动失败并打印错误信息。 加载

优点

  1. AOF可以更好的保护数据不丢失,一般AOF会以每隔 1 秒,通过后台的一个线程去执行一次fsync操作,如果 Redis 挂掉了,最多丢失 1 秒的数据(非常适合用来做热备)。
  2. AOFappend-only的模式写入,所以没有任何的磁盘寻址的开销,写入性能非常的高。
  3. AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。

缺点

  1. 对于同一份数据备份文件,AOFRDB大。
  2. AOF开启后支持写的QPS会比RDB支持的写的QPS低。因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的。
  3. 数据恢复比较慢,不适合做冷备。

本期就到这啦,有不对的地方欢迎好哥哥们评论区留言,另外求关注、求点赞

上一篇: 超详细解析 Redis 持久化之 RDB