持久化

一、RDB 持久化

可以将 redis 在内存中的数据库状态保存到磁盘里面,避免数据因为服务器进程退出而丢失。

RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个 RDB 文件中。

RDB 持久化功能所生成的 RDB 文件是一个经过压缩的二进制文件。有 SAVE 和 BGSAVE 命令

  • SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
  • BGSAVE 命令会派生出一个子进程,然后由子进程负责创建RDB 文件,服务器进程继续处理客户端命令
    • 当服务器执行 BGSAVE 时,客户端发送的SAVE、BGSAVE 命令会被服务器拒绝;BGREWRITEAOF 命令会被延迟到 BGSAVE 命令执行完毕之后执行。如果 BGREWRITEAOF 命令正在执行,客户端发送的 BGSAVE 命令会被拒绝。
    • BGREWRITEAOF 和 BGSAVE 两个命令都是由子进程执行,虽然没有冲突,但是两个子进程都同时执行大量的磁盘写入操作,影响性能。

RDB 文件的载入工作是在服务器启动时自动执行的,且为阻塞的。因为AOF文件的更新频率比RDB文件的更新频率高,所以服务器如果开启了AOF持久化功能,优先使用AOF文件还原数据库状态。只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件还原数据库状态

1. 保存条件

redis 通过配置定时去 BGSAVE。Redis 服务器会默认 100ms 执行一次检查 save 选项设置的保存条件是否满足。如果任意一个条件被满足,就执行 BGSAVE 命令。

  • 距离上次执行保存的时间超过条件所设置的时间
  • 数据库状态的修改次数超过条件所设置的次数
1
2
3
save 900 1  # 服务器在 900 秒之内,对数据库进行了至少1次修改,下同
save 300 100
save 60 10000
  • dirty 计数器记录距离上一次成功执行 SAVE 命令或者 BGSAVE 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(增删改)
  • lastsave 属性是一个 unix 时间戳,记录了服务器上一次成功执行 SAVE / BGSAVE 命令的时间

2. RDB 文件结构

1
| REDIS | db_version | databases | EOF | check_sum |
  • REDIS 部分是固定的 5 个字节,保存“REDIS”五个字符
  • db_version 长度为 4 个字节,是一个字符串表示的整数,比如“0006”
  • databases 部分包含着零个或者任意多个数据库,以及各个数据库中的键值对数据
  • EOF 常量的长度为 1 个字节,标志着 RDB 文件正文内容的结束
  • check_sum 是一个 8 字节长的无符号整数,保存着校验和,这个校验和是程序通过对前四个部分的内容进行计算得出的。在载入RDB文件时,会将载入数据所计算的校验和与 check_sum 所记录的校验和进行对比,判断是否出错或者损坏。

3. 分析 RDB 文件

1
2
3
# -c:以 ASCII 编码的方式打印输入文件
# -x:以十六进制的方式打印输入文件
od -c -x dump.rdb

4. 优缺点

优点:

  • 方便备份,可以方便的将一个 RDB 文件移动到其他的存储介质上
  • RDB 在恢复大数据集时速度比 AOF 的恢复速度要快
  • RDB 可以最大程序上保证 redis 的性能,父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 IO 操作

缺点:

  • RDB 文件需要保存整个数据集的状态,我们可以控制发生 RDB 的时间间隔,但是这个时间间隔内发生的故障停机,就会导致数据丢失
  • 每次保存 RDB 文件的时候,redis 都要 fork 出一个子进程来进行持久化操作,在数据集比较庞大时,fork 可能会非常耗时,造成服务器在 fork 的时间段无法处理客户端请求。

二、AOF 持久化

AOF 持久化通过保存 Redis 服务器所执行的 “写命令” 来记录数据库状态的。在 AOF 文件中,redis 服务器会自动添加 SELECT 命令用于指定某个数据库。AOF 功能的实现可以分为:命令追加、文件写入、文件同步三个步骤。

  1. 命令追加

    服务器执行完一个写命令之后,会以一定的协议格式将被执行的写命令追加到名为 aof_buf 的 AOF 缓冲区的末尾

  2. AOF 文件的写入和同步

    Redis 服务器进程是一个事件循环,服务器每次结束一个事件循环之前,都会考虑是否需要将 aof_buf 缓冲区的内容写入和保存到 AOF 文件里面。根据不同的配置(appendfsync 值)产生不同的持久化行为。

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

    注意:现代操作系统,当调用 write 函数写数据到文件时,操作系统通常将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正的将缓冲区中数据写入到磁盘。操作系统提供 fsync 完成同步操作。

这三种方式的效率和安全性分析:

  • Appendfsync 为 always 时,因为每个事件循环都要同步,所以效率最慢。但是这种方式安全性较高,因为即使出现故障停机,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。
  • Appendfsync 为 everysec 时,因为一秒同步一次,所以效率还好。并且就算出现故障停机,数据库也只丢失一秒钟的命令数据。
  • Appendfsync 为 no 时,因为无需手动执行同步操作,因此该模式下的 AOF 文件写入速度总是最快的;不过可能丢失的数据量比较多。

AOF 文件的载入与数据还原:AOF 文件包含了重建数据库状态所需的所有写命令,因此只需要创建一个不带网络的伪客户端(因为 redis 的命令只能在客户端上下文中执行,而载入 AOF 文件时所使用的命令直接来源于 AOF 文件而不是网络连接),通过这个客户端,读入并重新执行一遍 AOF 文件中保存的写命令,就可以还原服务器关闭之前的数据库状态。

1. AOF 重写

为什么要进行 AOF 重写

  • 随着 redis 服务器运行时间越长,AOF 文件中的内容会越来越多,可能会占用过多的磁盘空间;并且 AOF 文件体积越大,使用 AOF 文件来进行数据还原所需的时间就越多。
  • 为了解决 AOF 文件体积膨胀的问题,redis 会进行 AOF 文件重写。也就是会创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个 AOF 文件所保存的数据库状态相同,但新 AOF 文件不会包含任何浪费空间的冗余命令,所以新 AOF 文件的体积通常会比旧 AOF 文件的体积要小得多。

AOF 文件重写的实现

遍历所有数据库的所有键,忽略过期的键,根据键的类型获取到键包含的所有元素,组装构造 “写入语句”。然后写入新的 aof 文件。因为新的 AOF 文件只包含还原当前数据库状态所必须的命令,所以新 AOF 文件不会浪费任何硬盘空间。

需要注意的一个细节,在构造 “写入语句” 时,为了避免在执行命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,如果元素数量超过了一个阈值(一般为 64),那么重写程序将使用多条命令来记录键的值,而不单单使用一条命令。

命令:BGREWRITEAOF 进行 AOF 后台重写

Redis 会将 AOF 重写程序放到子进程执行。这样做达到两个目的:

  • 目的:子进程进行 AOF 重写期间,服务器进程可以继续处理客户端命令请求
  • 目的:子进程带有服务器进程的数据副本,使用子进程,可以在不使用锁的情况下,保证数据的安全性

产生问题:子进程在进行 AOF 重写期间,主服务器进程还需要继续处理客户端写命令请求,导致当前服务器的数据库状态和AOF文件所保存的数据库状态不一致。如何解决呢?

  • Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,他会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。
  • 这样可以保证,AOF 缓冲区的内容会定期被写入和同步到 AOF 文件,对现有 AOF 文件的处理工作会照常进行。并且从创建子进程开始,服务器执行的所有写命令都会被记录到 AOF 重写缓冲区中。

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

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

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

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

2. 优缺点

优点:

  • 可以设置不同的 fsync 刷盘策略,AOF 默认的策略是每秒 fsync 一次,这种配置下,redis 仍然可以保持良好的性能,即使发生故障停机,也只会丢失一秒钟的数据(fsync 会在后台线程执行,主线程可以继续处理客户端命令)
  • AOF 文件是一个只进行追加操作的日志文件,如果写入不完整的命令(比如写入时磁盘已满,写入中途停机等),redis 也提供了工具修复。当 AOF 文件体积较大时,会自动在后台对 AOF 进行重写,整个重写过程是绝对安全的。
  • AOF 存储的以 redis 协议格式的所有写入操作,因此文件内容的可读性比较高。比如:如果不小心执行了 flushall 命令,但只要 AOF 文件未被重写,只要停止服务器,移除 AOF 文件末尾的 FlushAll 命令,并重启 Redis,就可以恢复数据集到 FlushAll 执行之前的状态。

缺点:

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快

三、使用场景

  • 数据恢复:redis 服务异常,AOF 比 RDB 更利于数据恢复。AOF 默认每秒将数据量追加到文件末尾存盘一次,RDB 是一个时间点的数据快照,时间跨度比较大。
  • 数据备份:RDB 是 redis 内存数据快照,速度快,体积小。更适合数据备份存储
  • redis 服务启动速度:redis 启动加载 RDB 文件比 AOF 快。因为 AOF 文件可能有冗余命令,RDB 是数据集合
  • 持久化速度:AOF 默认每秒存盘和 RDB 持久化是异步存储,基本不影响主线程主逻辑功能。如果 AOF 采样写命令实时存盘,可能会严重影响 redis 服务性能
  • 集群节点间全量同步:需要拷贝服务进程的内存数据,根据 RDB 持久化特点:速度快、体积小。显然 RDB 更适合于集群间传输数据