【Redis】回炉重学 AOF

您所在的位置:网站首页 redis的aof原理 【Redis】回炉重学 AOF

【Redis】回炉重学 AOF

2023-11-17 23:29| 来源: 网络整理| 查看: 265

这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

AOF-思维导图.png

一、前言

Redis 的持久化主要有两大机制:

AOF 会记录过程, RDB 只管结果。

AOF(Append Only File)日志 RDB 快照

AOF ( append only file ):Redis 的另一种持久化方式。

Redis 默认情况下是不开启的。

开启 AOF 持久化后 Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件。

实战配置

配置 redis.conf

# 可以通过修改redis.conf配置文件中的appendonly参数开启 appendonly yes # AOF 文件的保存位置和RDB文件的位置相同, 都是通过dir参数设置的。 dir ./ # 默认的文件名是appendonly.aof,可以通过appendfilename参数修改 appendfilename appendonly.aof

二、原理

AOF 文件中存储的是 redis 的命令。

它是在命令执行后才记录日志, 所以不会阻塞当前的写操作。

AOF 日志采用 “写后”,意思是:Redis 是先执行命令,把数据写入内存,然后才记录日志。

比较熟悉的是数据库的写前日志(Write Ahead Log,WAL),也就是在实际写数据前,先把修改的数据记到日志文件中,以便故障时进行恢复。 例如,MySQL 中的 redo log(重做日志)

Redis 收到 set testkey testvalue 命令后记录的日志为例, 看看 AOF 日志的内容:

其中, *3 表示当前命令有三个部分, 每部分都是由 $+数字 开头, 后面紧跟着具体的命令、键或值。这里, “数字”表示这部分中的命令、键或值一共有多少字节。例如, $3 set 表示这部分有 3 个字节, 也就是 set 命令。y

如图:

2021-04-0716-11-20.png

工作原理图,如下: aof-重写工作原理.png

同步命令到 AOF 文件的整个过程可以分为三个阶段:

命令传播: Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。

当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。 服务器在接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文本转换为 Redis 字符串对象( StringObject)。 每当命令函数成功执行之后, 命令参数都会被传播到 AOF 程序。

缓存追加: AOF 程序根据接收到的命令数据, 将命令转换为网络通讯协议的格式, 然后将协议内容追加到服务器的 AOF 缓存中。

当命令被传播到 AOF 程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的协议文本。 协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。 redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF 文件的协议文本( RESP )。

文件写入和保存: AOF 缓存中的内容被写入到 AOF 文件末尾, 如果设定的 AOF 保存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用, 将写入的内容真正地保存到磁盘中。

每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:

WRITE: 根据条件, 将 aof_buf 中的缓存写入到 AOF 文件。 SAVE: 根据条件, 调用 fsync 或 fdatasync 函数, 将 AOF 文件保存到磁盘中。 AOF 保存模式

AOF 也有两个潜在的风险:

如果刚执行完一个命令, 还没有来得及记日志就宕机了, 那么这个命令和相应的数据就有丢失的风险。

AOF 虽然避免了对当前命令的阻塞, 但可能会给下一个操作带来阻塞风险。

这是因为: AOF 日志也是在主线程中执行的, 如果在把日志文件写入磁盘时, 磁盘写压力大, 就会导致写盘很慢, 进而导致后续的操作也无法执行了。

Redis 目前支持三种 AOF 保存模式:

NO(AOF_FSYNC_NO) 不保存:。 操作系统控制的写回: 每个写命令执行完, 只是先把日志写到 AOF 文件的内存缓冲区, 由操作系统决定何时将缓冲区内容写回磁盘。

Everysec(AOF_FSYNC_EVERYSEC) 每一秒钟保存一次(默认):。每个写命令执行完, 只是先把日志写到 AOF 文件的内存缓冲区, 每隔一秒把缓冲区中的内容写入磁盘;

Always(AOF_FSYNC_ALWAYS) ,每执行一个命令保存一次(不推荐):。同步写回: 每个写命令执行完, 立马同步地将日志写回磁盘;

对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下图:

2020-09-0420:10.png

1)不保存

在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。

在这种模式下, SAVE 只会在以下任意一种情况中被执行:

Redis 被关闭 AOF 功能被关闭 系统的写缓存被刷新

(可能是缓存已经被写满, 或者定期保存操作被执行)

这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

2)每一秒钟保存一次(推荐)

在这种模式中, SAVE 原则上每隔一秒钟就会执行一次。

因为 SAVE 操作是由后台子线程(fork)调用的, 所以它不会引起服务器主进程阻塞。

3)每执行一个命令保存一次

在这种模式下, 每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。

另外, 因为 SAVE 是由 Redis 主进程执行的, 所以在 SAVE 执行期间, 主进程会被阻塞, 不能接受命令请求。

AOF 保存模式对性能和安全性的影响。

三、AOF 重写

“性能问题”, 主要在于以下三个方面:

文件系统本身对文件大小有限制, 无法保存过大的文件 如果文件太大, 之后再往里面追加命令记录的话, 效率也会变低 如果发生宕机, AOF 中记录的命令要一个个被重新执行, 用于故障恢复, 如果日志文件太大, 整个恢复过程就会非常缓慢, 这就会影响到 Redis 的正常使用

说白了就是文件过大,会导致一些列问题,那么想办法把文件变小就行了。

那就需要 AOF 重写机制。

(1)重写

AOF 非阻塞的重写过程,如图:

aof-重写.png

Redis 可以在 AOF 体积变得过大时, 自动地在后台( Fork 子进程)对 AOF 进行重写。

重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。

所谓的 “重写” 其实是一个有歧义的词语, 实际上, AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。

2021-11-0716-11-34.png

举例如下:

set s1 11 变成 set s1 22 ------- > set s1 33 set s1 33 # 没有优化的: set s1 11 set s1 22 set s1 33 # 优化后: set s1 33 lpush list1 1 2 3 变成 lpush list1 4 5 6 -------- > list1 1 2 3 4 5 6 # 优化后 lpush list1 1 2 3 4 5 6

Redis 不希望 AOF 重写造成服务器无法处理请求, 所以 Redis 决定将 AOF 重写程序放到**(后台)子进程**里执行。

2021-04-0716-58-01.png

这样处理的最大好处是:

子进程进行 AOF 重写期间, 主进程可以继续处理命令请求。

子进程带有主进程的数据副本, 使用子进程而不是线程, 可以在避免锁的情况下, 保证数据的安全性。

使用子进程也有一个问题需要解决:

因为子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。

为了解决这个问题:

Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用, Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外, 还会追加到这个缓存中。

重写过程分析(整个重写操作是绝对安全的)

AOF 后台重写, 也即是 BGREWRITEAOF 命令(AOF 重写)的工作原理。

Redis 在创建新 AOF 文件的过程中, 会继续将命令追加到现有的 AOF 文件里面, 即使重写过程中发生停机, 现有的 AOF 文件也不会丢失。

而一旦新 AOF 文件创建完毕, Redis 就会从旧 AOF 文件切换到新 AOF 文件, 并开始对新 AOF 文件进行追加操作。

当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:

处理命令请求。

将写命令追加到现有的 AOF 文件中。 将写命令追加到 AOF 重写缓存中。 这样一来可以保证: 现有的 AOF 功能会继续执行, 即使在 AOF 重写期间发生停机, 也不会有任何数据丢失。 所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。

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

将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。 对新的 AOF 文件进行改名, 覆盖原有的 AOF 文件。

Redis 数据库里的 + AOF 重写过程中的命令 -------> 新的 AOF 文件----> 覆盖老的。

当步骤 1 执行完毕之后, 现有 AOF 文件、新 AOF 文件和数据库三者的状态就完全一致了。

当步骤 2 执行完毕之后, 程序就完成了新旧两个 AOF 文件的交替。

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

在整个 AOF 后台重写过程中, 只有最后的写入缓存和改名操作会造成主进程阻塞, 在其他时候, AOF 后台重写都不会对主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。

(2)触发方式 配置触发

在 redis.conf 中配置

# 表示当前 aof 文件大小超过上一次 aof 文件大小的百分之多少的时候会进行重写。 # 如果之前没有重写过, 以启动时aof文件大小为准 auto-aof-rewrite-percentage 100 # 限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化 auto-aof-rewrite-min-size 64mb 执行 bgrewriteaof 命令 127.0.0.1:6379> bgrewriteaof Background append only file rewriting started (3)混合持久化

RDB 和 AOF 各有优缺点, Redis 4.0 开始支持 rdb 和 aof 的混合持久化。

如果把混合持久化打开, aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。

RDB 的头+ AOF 的身体----> appendonly.aof

开启混合持久化

aof-use-rdb-preamble yes

AOF 文件是 rdb 文件的头和 aof 格式的内容, 在加载时, 首先会识别 AOF 文件是否以 REDIS 字符串开头, 如果是就按 RDB 格式加载, 加载完 RDB 后继续按 AOF 格式加载剩余部分。

四、AOF 文件的载入与数据还原

因为 AOF 文件里面包含了重建数据库状态所需的所有写命令, 所以服务器只要读入并重新执行一遍 AOF 文件里面保存的写命令, 就可以还原服务器关闭之前的数据库状态。

Redis 读取 AOF 文件并还原数据库状态的详细步骤如下:

创建一个不带网络连接的伪客户端(fake client)

因为 Redis 的命令只能在客户端上下文中执行, 而载入 AOF 文件时所使用的命令直接来源于 AOF 文件而不是网络连接, 所以服务器使用了一个没有网络连接的伪客户端来执行 AOF 文件保存的写命令, 伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样

从 AOF 文件中分析并读取出一条写命令

使用伪客户端执行被读出的写命令

一直执行步骤2 和 步骤3, 直到 AOF 文件中的所有写命令都被处理完毕为止。

当完成以上步骤之后, AOF 文件所保存的数据库状态就会被完整地还原出来。

整个过程如下图所示: aof-载入数据还原.png



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3