【Redis】 AOF 持久化

Metadata

title: 【Redis】 AOF 持久化
date: 2023-07-09 08:39
tags:
  - 行动阶段/完成
  - 主题场景/数据存储
  - 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
  - 细化主题/数据存储/Redis
categories:
  - 数据存储
keywords:
  - 数据存储/Redis
description: 【Redis】 AOF 持久化

概述

AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态。服务器启动时,可通过载入和执行 AOF 文件中保存的命令来还原服务器关闭前的数据库状态。

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

文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。

AOF 持久化的实现

AOF 持久化可分为命令追加,文件写入,文件同步三个步骤。

命令追加

开启 AOF 持久化后,服务器执行完一个写命令后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区末尾

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

AOF 文件的写入与同步

Redis 服务器进程就是一个事件循环,负责接收客户端命令请求及命令回复,时间事件负责执行向serverCron函数这样需要定时运行的函数。服务器每结束一个时间循环前,都会调用flushAppendOnlyFile函数,考虑是否有必要将 aof 缓冲区中的内容写入和保存至 AOF 文件里。这个判断的依据就是根据配置文件的 appendfsync 值决定:

  • always:将 aof_buf 缓冲区的所有内容写入并同步到 AOF 文件。
  • everysec:将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,如果上次同步 AOF 文件的时间超过一秒,就再次对 AOF 文件进行同步,并由一个线程专门负责。
  • no:将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统决定

为什么有写入和同步的区分?写入≠同步

为提高写效率,操作系统一般将写入数据暂时保存在内存缓冲区,等缓冲区填满或超过指定时间后才会真正地将数据同步到磁盘里。操作系统提供了 fsync 和 fdatasync 两同步函数,可强制操作系统同步数据,保证数据安全性。

也就是说,每一次的事件循环,aof_buf 中的指令都会被写入操作系统的缓冲区,根据 appendfsync 配置,当操作系统缓冲区满足一定条件后,就被真实地写入磁盘内。

AOF 文件的载入与数据还原

步骤如下:

  1. 创建一个没有网络连接的伪客户端。由于 Redis 命令只能在客户端上下文中执行,并且 AOF 文件在本地而不是网络。
  2. 解析 AOF 文件并取出一条写命令。
  3. 使用伪客户端执行被读出的写命令
  4. 持续执行 2 和 3,直到所有写命令都已经执行完毕

AOF 创建和重写时对过期键的处理套路

如果数据库中的某个键已经过期且没有被删除,AOF 文件不会因为这个对过期键产生影响。当过期间被惰性删除或定期删除后,AOF 文件追加一条 DEL 命令来显式删除。

AOF 重写时,程序会对数据库的键检查,已过期的不会保存到 AOF 文件中。

AOF 重写

因为 AOF 持久化会将所有的写命令都记录,所以会有冗余情况,比如频繁地创建删除键值对,或者对同一个键的值频繁更新,都会导致文件的内容越来越多。所以需要一种瘦身的机制确保 AOF 里存的都是必不可少的精华。

Redis 提供 AOF 文件重写功能,让服务器创建一个新的 AOF 文件,替代现有的 AOF 文件,减少冗余命令。

AOF 文件重写的实现

在新的 AOF 文件的重写过程中,不会读取旧 AOF 文件,而是通过读取数据库状态来实现的。首先从数据库中读取键现在的值,然后用一条命令记录键值对,代替之前记录的多条命令。

注:在重写时会先检查键所包含的元素数量,因为多元素的键在命令转换时可能会导致客户端输入缓冲区溢出。因此读取配置中对应的常量,默认超过 64 个就用多条指令记录。

AOF 后台重写过程

AOF 重写的过程中会有大量的写入操作,为了避免 Redis 服务器长时间的阻塞,重写工作将被放到子进程中进行。这样的好处是:

  • 父进程仍然可继续处理请求。
  • 子进程有自己的数据副本,而非子线程,可以避免一些线程安全性问题的出现。

子进程在执行 AOF 重写期间,服务器进程还需要继续处理命令请求,而新的命令可能会对现有的数据库状态进行修改,导致当前数据库状态与重写后的 AOF 文件保存状态不一致。为解决这个问题,设置了 AOF 重写缓冲区

当重写子进程创建后,Redis 服务器执行完写命令就会将其写入 AOF 缓冲区和 AOF 重写缓冲区,子进程执行重写期间,服务器进程要执行 3 个工作:

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

当子进程完成重写后,会向父进程发送一个信号,父进程接收并调用信号处理函数,将重写缓冲区的所有内容写到新 AOF 文件中,原子地覆盖现有的 AOF 文件。因此整个 AOF 文件重写的过程中,只有信号处理函数执行时,才会阻塞,将性能损耗降到最低。