专栏首页用户4352451的专栏redis的持久化存储AOF的原理

redis的持久化存储AOF的原理

背景

上篇文章我们将了RDB的原理,这节来看看AOF。 AOF字面的意思是,append only file仅追加文件。 AOF 是以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。是不是和mysql的binlog日志模式还是有点类似 mysql的binlog是记录所有数据库表结构变更(例如CREATE、ALTER TABLE…)以及表数据修改(INSERT、UPDATE、DELETE…)的二进制日志。

AOF

AOF是通过记录写入的命令来同步和恢复数据的。那就先从这么一个过程入手,他是怎么一个实现过程呢?

AOF运行的底层步骤

1. 命令传播(命令从客户端到AOF程序的过程)

  1. 当我们在客户端执行一个命令的时候,通过网络连接让redis服务端接受命令,它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文本转换为 Redis 字符串对象(StringObject)。当命令参数执行成功的时候会将命令参数传播到AOF程序中。然后对其进行下一步操作,那就是将命令参数转换为协议文本进行存储。
  1. 这是有问题的,当AOF程序挂掉的时候该怎么办?那岂不是缓存失败了

2.缓存追加

  1. 上面也说到了,当执行完操作命令的时候,会将执行的参数命令传播到AOF程序,
  • 这个时候AOF程序会将命令参数转回原来的网络协议文本。
  • 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。

3. 文件的写入和保存

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

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

两个步骤都需要根据一定的条件来执行, 而这些条件由 AOF 所使用的保存模式来决定, 以下小节就来介绍 AOF 所使用的三种保存模式, 以及在这些模式下, 步骤 WRITE 和 SAVE 的调用条件。

4. AOF 保存模式

Redis 目前支持三种 AOF 保存模式,它们分别是:

AOF_FSYNC_NO :不保存。 AOF_FSYNC_EVERYSEC :每一秒钟保存一次。 AOF_FSYNC_ALWAYS :每执行一个命令保存一次

不保存

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

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

Redis 被关闭 AOF 功能被关闭 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行) 这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

每秒执行一次

在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。在上一句的说明里面使用了词语“原则上”, 在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。

每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:

子线程正在执行 SAVE ,并且:

这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。 这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。 子线程没有在执行 SAVE ,并且:

上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。 上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。 可以用流程图表示这四种情况: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h4ySGKhY-1591519560632)(http://note.youdao.com/yws/res/14486/A01447314E604073B6C7B8EC9CA92AD8)] 根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。

如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。

Redis 官网上所说的, AOF 在“每一秒钟保存一次”时发生故障, 只丢失 1 秒钟数据的说法, 实际上并不准确。

每执行一个命令保存一次

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

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

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

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

不保存(AOF_FSYNC_NO):写入和保存都由主进程执行,两个操作都会阻塞主进程。 每一秒钟保存一次(AOF_FSYNC_EVERYSEC):写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。 每执行一个命令保存一次(AOF_FSYNC_ALWAYS):和模式 1 一样。 因为阻塞操作会让 Redis 主进程无法持续处理请求, 所以一般说来, 阻塞操作执行得越少、完成得越快, Redis 的性能就越好。

模式 1 的保存操作只会在AOF 关闭或 Redis 关闭时执行, 或者由操作系统触发, 在一般情况下, 这种模式只需要为写入阻塞, 因此它的写入性能要比后面两种模式要高, 当然, 这种性能的提高是以降低安全性为代价的: 在这种模式下, 如果运行的中途发生停机, 那么丢失数据的数量由操作系统的缓存冲洗策略决定。

模式 2 在性能方面要优于模式 3 , 并且在通常情况下, 这种模式最多丢失不多于 2 秒的数据, 所以它的安全性要高于模式 1 , 这是一种兼顾性能和安全性的保存方案。

模式 3 的安全性是最高的, 但性能也是最差的, 因为服务器必须阻塞直到命令信息被写入并保存到磁盘之后, 才能继续处理请求。

综合起来,三种 AOF 模式的操作特性可以总结如下:

模式

WRITE 是否阻塞?

SAVE 是否阻塞?

停机时丢失的数据量

AOF_FSYNC_NO

阻塞

阻塞

操作系统最后一次对 AOF 文件触发 SAVE 操作之后的数据。

AOF_FSYNC_EVERYSEC

阻塞

不阻塞

一般情况下不超过 2 秒钟的数据。

AOF_FSYNC_ALWAYS

阻塞

阻塞

最多只丢失一个命令的数据。

AOF 文件的读取和数据还原

AOF 文件保存了 Redis 的数据库状态, 而文件里面包含的都是符合 Redis 通讯协议格式的命令文本。

这也就是说, 只要根据 AOF 文件里的协议, 重新执行一遍里面指示的所有命令, 就可以还原 Redis 的数据库状态了。

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

创建一个不带网络连接的伪客户端(fake client)。 读取 AOF 所保存的文本,并根据内容还原出命令、命令的参数以及命令的个数。 根据命令、命令的参数和命令的个数,使用伪客户端执行该命令。 执行 2 和 3 ,直到 AOF 文件中的所有命令执行完毕。 完成第 4 步之后, AOF 文件所保存的数据库就会被完整地还原出来。

注意, 因为 Redis 的命令只能在客户端的上下文中被执行, 而 AOF 还原时所使用的命令来自于 AOF 文件, 而不是网络, 所以程序使用了一个没有网络连接的伪客户端来执行命令。 伪客户端执行命令的效果, 和带网络连接的客户端执行命令的效果, 完全一样。

整个读取和还原过程可以用以下伪代码表示:

def READ_AND_LOAD_AOF():

# 打开并读取 AOF 文件
file = open(aof_file_name)
while file.is_not_reach_eof():

    # 读入一条协议文本格式的 Redis 命令
    cmd_in_text = file.read_next_command_in_protocol_format()

    # 根据文本命令,查找命令函数,并创建参数和参数个数等对象
    cmd, argv, argc = text_to_command(cmd_in_text)

    # 执行命令
    execRedisCommand(cmd, argv, argc)

# 关闭文件
file.close()

作为例子, 以下是一个简短的 AOF 文件的内容:

*2
$6
SELECT
$1
0
*3
$3
SET
$3
key
$5
value
*8
$5
RPUSH
$4
list
$1
1
$1
2
$1
3
$1
4
$1
5
$1
6

当程序读入这个 AOF 文件时, 它首先执行 SELECT 0 命令 —— 这个 SELECT 命令是由 AOF 写入程序自动生成的, 它确保程序可以将数据还原到正确的数据库上。

然后执行后面的 SET key value 和 RPUSH 1 2 3 4 命令, 还原 key 和 list 两个键的数据。 为了避免对数据的完整性产生影响, 在服务器载入数据的过程中, 只有和数据库无关的订阅与发布功能可以正常使用, 其他命令一律返回错误

AOF 重写

AOF 文件通过同步 Redis 服务器所执行的命令, 从而实现了数据库状态的记录, 但是, 这种同步方式会造成一个问题: 随着运行时间的流逝, AOF 文件会变得越来越大。

举个例子, 如果服务器执行了以下命令:

RPUSH list 1 2 3 4      // [1, 2, 3, 4]

RPOP list               // [1, 2, 3]

LPOP list               // [2, 3]

LPUSH list 1            // [1, 2, 3]

那么光是记录 list 键的状态, AOF 文件就需要保存四条命令。

另一方面, 有些被频繁操作的键, 对它们所调用的命令可能有成百上千、甚至上万条, 如果这样被频繁操作的键有很多的话, AOF 文件的体积就会急速膨胀, 对 Redis 、甚至整个系统的造成影响。

为了解决以上的问题, Redis 需要对 AOF 文件进行重写(rewrite): 创建一个新的 AOF 文件来代替原有的 AOF 文件, 新 AOF 文件和原有 AOF 文件保存的数据库状态完全一样, 但新 AOF 文件的体积小于等于原有 AOF 文件的体积。

以下就来介绍 AOF 重写的实现方式。

AOF 重写的实现

AOF 重写可以由用户通过调用 BGREWRITEAOF 手动触发。

另外, 服务器在 AOF 功能开启的情况下, 会维持以下三个变量:

记录当前 AOF 文件大小的变量 aof_current_size 。 记录最后一次 AOF 重写之后, AOF 文件大小的变量 aof_rewrite_base_size 。 增长百分比变量 aof_rewrite_perc 。 每次当 serverCron 函数执行时, 它都会检查以下条件是否全部满足, 如果是的话, 就会触发自动的 AOF 重写:

没有 BGSAVE 命令在进行。 没有 BGREWRITEAOF 在进行。 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)。 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。 默认情况下, 增长百分比为 100% , 也即是说, 如果前面三个条件都已经满足, 并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话, 那么触发自动 AOF 重写。

总结

  • AOF 文件通过保存所有修改数据库的命令来记录数据库的状态。
  • AOF 文件中的所有命令都以 Redis 通讯协议的格式保存。 不同的 AOF 保存模式对数据的安全性、以及 Redis 的性能有很大的影响。
  • AOF 重写的目的是用更小的体积来保存数据库状态,整个重写过程基本上不影响 Redis 主进程处理命令请求。
  • AOF 重写是一个有歧义的名字,实际的重写工作是针对数据库的当前值来进行的,程序既不读写、也不使用原有的 AOF 文件。
  • AOF 可以由用户手动触发,也可以由服务器自动触发。

参考

参考文章: https://redisbook.readthedocs.io/en/latest/internal/aof.html 参考文章: https://blog.csdn.net/weixin_40413961/article/details/106596413 大部分来源于文章1.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • BigDecimal大小判断

    equals源码: public boolean equals(Object anObject) {//name2传入equals方法,anObject指向n...

    居士
  • JVM面试题

    2) 每个方法执行都会创建一个栈帧,用于存放局部变量表,操作栈,动态链接,方法出口等。每个方法从被调用,直到被执行完。对应着一个栈帧在虚拟机中从入栈到出栈的过...

    居士
  • ThreadLocal浅入浅出

    ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,...

    居士
  • Redis AOF 持久化详解

    Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。但是一旦进程退出,Redis 的数据就会丢失。

    程序员历小冰
  • Redis AOF 持久化详解

    Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。但是一旦进程退出,Redis 的数据就会丢失。

    程序员历小冰
  • Redis AOF 持久化详解

    Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快很多。但是一旦进程退出,Redis 的数据就会丢失。

    黄泽杰
  • Redis技术知识总结之六——Redis持久化机制

    Redis 有两种持久化机制:快照 (RDB) 和 AOF 日志。其中快照是一次性全量备份,AOF 是增量备份。

    剑影啸清寒
  • Java编程思想学习录(连载之:一切都是对象)

    CodeSheep
  • 一些流行Java MVC框架的调用栈

    老码农
  • ​反射基础之Enum

    因为枚举也是一个类,所以也可以通过Field,Method,Constructor的反射API获取其他信息:

    代码拾遗

扫码关注云+社区

领取腾讯云代金券