《Reids 设计与实现》第八章 AOF持久化

文章目录

  • 《Reids 设计与实现》第八章 AOF持久化
  • 一、简介
  • 二、AOF 持久化的实现
    • 1.命令追加
    • 2.AOF 文件的写入与同步
  • 三、AOF 文件的载入与数据还原
  • 四、AOF 重写
    • 1.AOF 文件重写的实现
    • 2.AOF 后台重写
  • 五、重点回顾

一、简介

除了 RDB 持久化功能之外,Redis 还提供了 AOF(Append Only File)持久化功能。与 RDB 持久化通过保存数据库中的键值对来记录数据库状态不同,AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态的,如图 11-1 所示

举个例子,如果我们对空白的数据库执行以下写命令,那么数据库中将包含三个键值对:

redis>SET msg "hello"
OKredis>SADD fruits "apple" "banana" "cherry"
(integer) 3redis>RPUSH numbers 128 256 512
(integer) 3

RDB 持久化保存数据库状态的方法是将 msg、fruits、numbers 三个键的键值对保存到 RDB 文件中,而 AOF 持久化保存数据库状态的方法则是将服务器执行的 SET、SADD、RPUSH 三个命令保存到 AOF 文件中

被写入 AOF 文件的所有命令都是以 Redis 的命令请求协议格式保存的,因为 Redis 的命令请求协议是纯文本格式,所以我们可以直接打开一个 AOF 文件,观察里面的内容

例如,对于之前执行的三个写命令来说,服务器将产生包含以下内容的 AOF 文件:

*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
*3\r\n$3\r\nSET\r\n$3\r\nmsg\r\n$5\r\nhello\r\n
*5\r\n$4\r\nSADD\r\n$6\r\nfruits\r\n$5\r\napple\r\n$6\r\nbanana\r\n$6\r\ncherry\r\n
*6\r\n$5\r\n$7\r\nnumbers\r\n$3\r\n128\r\n$3\r\n128\r\n$3\r\n256\r\n$3\r\n512\r\n

在这个 AOF 文件里面,除了用于指定数据库的 SELECT 命令是服务器自动添加的之外,其他都是我们之前通过客户端发送的命令

二、AOF 持久化的实现

AOF 持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤

1.命令追加

当 AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾

struct redisServer{//...//AOF 缓冲区sds aof_buf;//...
};

举个例子,如果客户端向服务器发送以下命令:

redis>SET KEY VALUE
OK

那么服务器在执行这个 SET 命令之后,会将以下协议内容追加到 aof_buf 缓冲区的末尾

*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

2.AOF 文件的写入与同步

Redis 的服务器进程就是一个事件循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间时间则负责执行像 serverCron 函数这样需要定时运行的函数

因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到 aof_buf 缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用 flushAppendOnlyFile 函数,考虑是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件里面,这个过程可以用以下伪代码表示:

def eventLoop():while True:#处理文件事件,接收命令请求以及发送命令回复#处理命令请求时可能会有新内容被追加到 aof_buf 缓冲区中processFileEvents()#处理时间事件processTimeEvents()#考虑是否要将 aof_buf 中的内容写入和保存到 AOF 文件里面flushAppendOnlyFile()

flushAppendOnlyFile 函数的行为由服务器配置的 appendfsync 选项的值来决定,各个不同值产生的行为如下表所示:

appendfsync 选项的值 flushAppendOnlyFile 函数的行为
always 将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件
everysec 将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间距离现在超过一秒钟,那么再次对 AOF 文件进行同步,并且这个同步操作是由一个线程专门负责执行的
no 将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统来决定

如果用户没有主动为 appendfsync 选项设置值,那么 appendfsync 选项的默认值为 everysec没关于 appendfsync 选项的更多信息,请参考 Redis 项目附带的示例配置文件

文件的写入和同步

为了提高文件的写入效率,在现代操作系统中,当用户调用 write 函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面

这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失

为此,系统提供了 fsync 和 fdatasync 两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性

AOF 持久化的效率和安全性

服务器配置 appendfsync 选项的值直接决定 AOF 持久化功能的效率和安全性

  • 当 appendfsync 的值为 always 时,服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,并且同步 AOF 文件,所以 always 的效率是 appendfsync 选项三个值当中最慢的一个,但从安全性来说,always 也是最安全的,因为即使出现故障停机,AOF 持久化也只会丢失一个事件循环中所产生的命令数据
  • 当 appendfsync 的值为 everysec 时,服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,并且每隔一秒就要在子线程中对 AOF 文件进行一次同步。从效率上来讲,everysec 模式足够快,并且就算出现故障停机,数据库也只丢失一秒钟的命令数据
  • 当 appendfsync 的值为 no 时,服务器在每个事件循环都要将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,至于何时对 AOF 文件进行同步,则由操作系统控制。因为处于 no 模式下的 flushAppendOnlyFile 调用无须执行同步操作,所以该模式下的 AOF 文件写入速度总是最快的,不过因为这种模式会在系统缓存中积累一段时间的写入数据,所以该模式的单次同步时长通常时三种模式中时间最长的。从平摊操作的角度来看,no 模式和 everysec 模式的效率类似,当出现故障停机时,使用 no 模式的服务器将丢失上次同步 AOF 文件之后的所有写命令数据

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

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

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

  1. 创建一个不带网络连接的伪客户端(fake client):因为 Redis 的命令只能在客户端上下文中执行,而载入 AOF 文件时所使用的命令直接来源于 AOF 文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行 AOF 文件保存的写命令,伪客户端执行命令的效果和网络连接的客户端执行命令的效果完全一样
  2. 从 AOF 文件中分析并读取出一条写命令
  3. 使用伪客户端执行被读出的写命令
  4. 一直执行步骤 2 和步骤 3,直到 AOF 文件中的所有写命令都被处理完毕为止

当完成以上步骤之后,AOF 文件所保存的数据库状态就会被完整地还原出来,整个过程如图 11-2 所示

四、AOF 重写

因为 AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF 文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的 AOF 文件很可能对 Redis 服务器、甚至整个宿主计算机造成影响,并且 AOF 文件的体积越大,使用 AOF 文件来进行数据还原所需的时间就越多

举个例子,如果客户端执行了以下命令:

redis>RPUSH list "A" "B"
(integer) 2redis>RPUSH list "C"
(integer) 3redis>RPUSH list "D" "E"
(integer) 5redis>LPOP list
"A"redis>LPOP list
"B"redis>RPUSH list "F" "G"
(integer) 5

那么光是为了记录这个 list 键的状态,AOF 文件就需要保存六条命令

对于实际的应用程序来说,写命令执行的次数和频率会比上面的简单示例要高得多,所以造成的问题也会严重得多

为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写(rewrite)功能。通过该功能,Redis 服务器可以创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多

1.AOF 文件重写的实现

虽然 Redis 将生成新 AOF 文件替换旧 AOF 文件的功能命名伪 “AOF 文件重写”,但实际上,AOF 文件重写并不需要对现有的 AOF 文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据库状态来实现的

还是上面那个 list 键的例子,如果服务器想要用尽量少的命令来记录 list 键的状态,那么最简单高效的办法不是去读取和分析现有 AOF 文件的内容,而是直接从数据库中读取键 list 的值,然后用一条 RPUSH list “C” “D” “E” “F” “G” 命令来代替保存在 AOF 文件中的六条命令,这样就可以将保存 list 键所需的命令从六条减少为一条了

除了列表键,其他所有类型的键都可以用同样的方法去减少 AOF 文件中的命令数量。首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是 AOF 重写功能的实现原理

在实际中,为了避免在执行命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,如果元素的数量超过了 REDIS_AOF_REWRITE_ITEMS_PER_CMD 常量的值,那么重写程序将使用多条命令来记录键的值,而不单单使用一条命令。假设 REDIS_AOF_REWRITE_ITEMS_PER_CMD 的值伪 64 ,那么如果一个集合键包含了超过 64 个元素,那么重写程序会用多条 SADD 命令来记录这个集合,并且每条命令设置的元素数量也为 64 个

2.AOF 后台重写

上面介绍的 AOF 重写程序 aof_rewrite 函数可以很好地创建一个新 AOF 文件的任务,但是,因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞,因为 Redis 服务器使用单个线程来处理命令请求,所以如果由服务器直接调用 aof_rewrite 函数的话,那么在重写 AOF 文件期间,服务器将无法处理客户端发来的命令请求

很明显,作为一种辅佐性的维护手段,Redis 不希望 AOF 重写造成服务器无法处理请求,所以 Redis 决定将 AOF 重写程序放到子进程里执行,这样做可以同时达到两个目的:

  • 子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理命令请求
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性

不过,使用子进程也有一个问题需要解决,因为子进程在进行 AOF 重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,从而使得服务器当前的数据库状态和重写后的 AOF 文件所保存的数据库状态不一致

为了解决这种数据不一致的问题,Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区,如图 11-4 所示

这也就是说,在子进程执行 AOF 重写期间,服务器进程需要执行以下三个工作:

  1. 执行客户端发来的命令
  2. 将执行后的写命令追加到 AOF 缓冲区
  3. 将执行后的写命令追加到 AOF 重写缓冲区

这样一来可以保证:

  • AOF 缓冲区的内容会定期被写入和同步到 AOF 文件,对现有 AOF 文件的处理工作会如常执行
  • 从创建子进程开始,服务器执行的所有写命令都会被记录到 AOF 重写缓冲区里面

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

  1. 将 AOF 重写缓冲区中的所有内容写入到新 AOF 文件中,这时新 AOF 文件所保存的数据库状态将和服务器当前的数据库状态一直
  2. 对新的 AOF 文件进行改名,原子地覆盖现有的 AOF 文件,完成新旧两个 AOF 文件的替换

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

在整个 AOF 后台重写过程中,只有信号处理函数执行时会对服务器进程(父进程)造成阻塞,在其他时候,AOF 后台重写都不会阻塞父进程,这将 AOF 重写对服务器性能造成的影响降到了最低

五、重点回顾

  • AOF 文件通过保存所有修改数据库的写命令请求来记录服务器的数据库状态
  • AOF 文件中的所有命令都以 Redis 命令请求协议的格式保存
  • 命令请求会先保存到 AOF 缓冲区里面,之后再定期写入并同步到 AOF 文件
  • appendfsync 选项的不同值对 AOF 持久化功能的安全性以及 Redis 服务器的性能有很大的影响
  • 服务器只要载入并重新执行保存在 AOF 文件中的命令,就可以还原数据库本来的状态
  • AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小
  • AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作
  • 在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作

《Reids 设计与实现》第八章 AOF持久化相关推荐

  1. 《Reids 设计与实现》第十八章 事务

    <Reids 设计与实现>第十八章 事务 文章目录 <Reids 设计与实现>第十八章 事务 一.简介 二.事务的实现 1.事务开始 2.命令入队 3.事务队列 4.执行事务 ...

  2. 《Reids 设计与实现》第七章 RDB 持久化

    <Reids 设计与实现>第七章 RDB 持久化 文章目录 <Reids 设计与实现>第七章 RDB 持久化 一.简介 二.RDB 文件的创建与载入 1.SAVE 命令执行时的 ...

  3. 《Redis设计与实现》之第十一章:AOF持久化

    AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的.被写入AOF文件的所有命令都是以Redis的命令请求协议格式(纯文本)保存的. 一,AOF持久化的实现 1.命令追加 当AOF持 ...

  4. Redis设计与实现AOF持久化

    什么是AOF持久化? 在前边也说了RDB持久化,AOF(Append only File)也是保存数据库状态的一种方式.它和RDB持久化的区别就是RDB是存的数据,而AOF存的是命令. AOF的实现 ...

  5. 《Redis设计与实现》第十一章 AOF持久化

    第十一章 AOF持久化 11.1 AOF持久化的实现 AOF持久化功能实现分为命令追加.文件写入.文件同步三个步骤. 11.1.1 命令追加 当AOF持久化功能出于打开状态时,服务器执行完一个写命令之 ...

  6. 《Reids 设计与实现》第十四章 集群(上)

    <Reids 设计与实现>第十四章 集群(上) 文章目录 <Reids 设计与实现>第十四章 集群(上) 一.简介 二.节点 1.启动节点 2.集群数据结构 3.CLUSTER ...

  7. 《Reids 设计与实现》第十一章 服务器

    <Reids 设计与实现>第十一章 服务器 文章目录 <Reids 设计与实现>第十一章 服务器 一.简介 二.命令请求的执行过程 1.发送命令请求 2.读取命令请求 3.命令 ...

  8. 《Reids 设计与实现》第九章 事件

    <Reids 设计与实现>第九章 事件 文章目录 <Reids 设计与实现>第九章 事件 一.简介 二.文件事件 1.文件事件处理器的构成 2.I/O 多路复用程序的实现 3. ...

  9. 《Reids 设计与实现》第六章 数据库

    <Reids 设计与实现>第六章 数据库 文章目录 <Reids 设计与实现>第六章 数据库 一.服务器中的数据库 二.切换数据库 三.数据库键空间 四.设置键的生存时间或过期 ...

最新文章

  1. 浅谈java中的四个核心概念【转】
  2. Laravel 单设备登录
  3. Atom不能补全原生JS的一些DOM函数
  4. github下载速度慢解决方法
  5. python基础语法有哪些-Python基础语法一
  6. RAC RMAN 通道配置 RMAN-12001 RMAN-12001 RMAN-10008 RMAN-10003 ORA-01017 错误
  7. 一种提高单片机i/o口驱动能力的方法
  8. pytorch 中 Autograd(四)
  9. 从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器
  10. PHP扩展调用C++静态库
  11. stc89c51单片机音乐盒系统设计_基于单片机的火控系统语音报读设计
  12. MongoDB 计划从“Data Sprawl”中逃脱!
  13. 春季:@Component与@Bean
  14. ios 推送通知服务证书不受信任(Apple Push Service certificate is not trusted)
  15. 《metapath2vec: Scalable Representation Learning for Heterogeneous Networks》
  16. xshell中出现的绿色背景的文件夹
  17. 浅谈SAP顾问未来十年在中国的发展前景
  18. 计算机软硬件问题及解决方法(经验篇)
  19. jmeter 保存响应到文件
  20. 基于影像基因的肺结节分割、肺癌分类分期、CT影像预测基因突变 --董云云 论文阅读

热门文章

  1. mysql中两列拼接_python之Pandas读写操作mysql数据库
  2. 【FileOutputStream类:文档中的换行与追加】
  3. FLIP-24+-+SQL+Client
  4. python_str 字符串的所有方法
  5. Oracle学习(四)_SQL函数
  6. MOSS中代码运行的权限提升(半摘)
  7. qprocess 最小化启动外部程序_安川机器人预约启动功能
  8. Redis之key的淘汰策略
  9. matlab 垂直边缘检测,matlab 边缘检测
  10. (205)硬件实现多时钟设计