聊聊 Redis 高可用之持久化AOF和RDB分析

Redis 持久化概述

Redis 是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将 Redis 中的数据以某种形式把内存中的数据保存到磁盘中;当 Redis 重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

Redis 提供了两种不同的持久化方法来讲数据存储到硬盘上 :

  • RDB:一种称为快照的方式,是 将某一时刻内存中的所有数据以快照的形式写入硬盘上。
  • AOF:一种称为只追加文件的方式,它会是将每次执行的写命令以追加的形式保存到硬盘上。

下面依次介绍 RDB 持久化和 AOF 持久化。

RDB 持久化

RDB ( Redis Database ) 持久化就是把存储在内存里的数据在某个时间点上写入到硬盘上。在创建快照之后,用户可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本,也可以当Redis 重新启动时,读取快照文件恢复数据。

1 创建快照的方式

  • 可以在客户端通过向 Redis 发送 BGSAVE 命令创建快照。接到命令后,Redis 会调用 fork 来创建一个子进程,然后子进程负责将快照写入硬盘,而父进程可以继续处理命令请求。
  • 可以在客户端通过向 Redis 发送 SAVE 命令来创建快照,接到 SAVE 命令后,Redis 服务器直接在主进程中创建快照,在创建快照期间,Redis 服务不再响应任何其他命令。
  • redis.conf 配置文件中设置了 save 配置选项, 比如 save 60 10000, 是指当“60 秒之内有10000次写入” 这个条件满足是,Redis 会自动触发 BGSAVE 命令。如果设置了多个 save 配置选项,那么当任意一个满足是,Redis 就会触发一次 BGSAVE 命令。
  • 当 Redis 接收到 SHUTDOWN 命令关闭服务器请求时,或者接收到标准 TERM 信号时,会执行 SAVE 命令,阻塞所有客户端请求,并在 SAVE 命令执行完毕之后关闭服务器。
  • 当一个 Redis 服务器第一次连接另一个 Redis 服务器,并向对方发送 psync 命令来开始复制操作时,如果主服务当前没有正在执行 BGSAVE 操作,那么主服务器就会执行 BGSAVE 命令。

2 原理分析

上文我们提到了创建 RDB 快照的几种方式,总结可以发现这几种方式无非就是通过 SAVEBGSAVE 命令来生成快照。

  • SAVE 命令: 此命令是在 Redis 服务器的主进程中执行,我们知道 Redis 是单线程的,那么,在执行此命令是就会阻塞当前 Redis 服务器,而无法执行其他命令的请求,对于内存比较大的实例会造成长时间阻塞。
  • BGSAVE 命令:此命令是 Redis 执行 fork 操作创建子进程,RDB 持久化过程有子进程处理。阻塞只发生在 fork 阶段,fork 结束后,Redis 主进程还可以继续处理其他命令请求。

由于 SAVE 命令会造成长时间阻塞,而 BGSAVE 命令阻塞时间较短,所以,一般很少使用SAVE 命令,而是用BGSAVE 命令,接下来我们进一步讲解一下 BGSAVE 命令。

虽然 SAVE 命令一直阻塞 Redis 直到快照生成完毕, 但是因为它不需要创建子进程, 并且没有子进程争抢资源,所以 SAVE 创建快照的速度比 BGSAVE 创建快照的速度快些。

fork系统调

fork 系统调用会产生一个子进程,它与父进程共享相同的内存地址空间,这样子进程在这一时刻就能拥有与父进程的相同的内存数据。

虽然子进程与父进程共享同一块内存地址空间,但是在 fork 子进程时,操作系统需要拷贝父进程的内存页表给子进程,如果整个 Redis 实例内存占用很大,那么它的内存页也会很大,在拷贝是就会比较耗时,同时这个过程会消耗大量的 CPU 资源。在完成拷贝之前父进程也处于阻塞状态,无法处理客户端请求。

fork 执行完之后,子进程就可以扫描自身所有的内存数据,然后把全部数据写入到 RDB 文件中。

fork 操作的流程如下所示:

在这里插入图片描述

Copy On Write

前面介绍了 BGSAVE 命令不会造成阻塞主进程接收其他命令的请求,有 fork 系统调用可知,子进程共享主进程的内存数据,那么,当 Redis 服务器接收到写命令时,修改内存数据时,子进程读取同一内存地址的数据时,就会出现脏数据。

如果为了快照而暂停写操作,肯定是不能接受的。所以,Redis 借助了操作系统提供的写时复制技术(Copy-On-Write, COW),在执行快照的同时,正常处理写操作。

当父进程接收到写操作时,如果要修改的数据在内存中已经存在,那么,主进程就会拷贝一份数据,重新分配新的内存地址空间,这样,父进程就在新申请的内存空间中修改数据,不在与子进程共享,这个过程就是 Copy On Write(写实复制)。

比如我们修改上图的 物理页13 的数据,Copy On Write 后的图如下所:

在这里插入图片描述

这样父子进程的内存就会逐渐分离,父进程申请新的内存空间并更改内存数据,子进程的内存数据不受影响。

由此可以看出,在生成 RDB 文件时,不仅消耗 CPU 资源,还有需要占用最多一倍的内存空间。所以,我们应该保证 Redis 机器拥有 足够的CPU和内存资源,并合理设置生成 RDB 的时机。

3 RDB 的优缺点

RDB 的优点:

  • RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适用于备份,全量复制等场景。
  • RDB 是派生子进程来生成快照文件,最大限度的减少了父进程阻塞时间,从而 RDB 最大限度的提高了 Redis 的性能。
  • Redis 加载 RDB 恢复数据要比 AOF 快的多。

RDB 的缺点:

  • RDB 方式没办法做到实时持久化 / 秒级持久化。会造成数据丢失。
  • RDB 需要 fork 创建子进程,进行持久化,如果数据集很大,CPU 性能不好,可能会导致 Redis 阻塞服务几毫秒甚至一秒钟,频繁执行成本过高。

AOF 持久化

AOF 全称成为 Append Only File ( 只追加文件)。简单来说,AOF 持久化会把执行的写命令写到 AOF 文件的末尾,以此来记录数据发生的变化。因此,Redis 只要从头到尾执行一次 AOF 文件中的文件,就可以恢复数据。

1 开启 AOF

Redis 中 AOF 默认是关闭的,在 redis.conf 配置文件中添加如下配置开启。

# 开启AOF
appendonly yes

# AOF文件名
appendfilename "appendonly.aof"

# 文件刷盘方式
appendfsync everysec

2 文件同步

开启 AOF 后,所有的写入命令都会追加到 AOF缓冲区 中, Redis 会根据刷盘策略把 AOF 缓冲区中的数据保存到磁盘中,为了保证数据文件的安全性,Redis 提供了如下刷盘策略:

  • appendfsync always:每个 Redis 写命令都会被写入硬盘,在一般 SATA 硬盘上,Redis 只能支持大约几百的 TPS 写入,对性能影响大;而 SSD 硬盘能处理几万的TPS,但是频繁的吸入会造成 SSD 硬盘的寿命缩短。所以 always 占用磁盘 I/O 比较高,数据安全性高。
  • appendfsync everysec:Redis 每秒对 AOF 文件进行同步一次。对性能影响较小,Redis 宕机时最多丢失 1 秒钟的数据。一般建议配置此种刷盘策略。
  • appendfsync no:按照操作系统的机制刷盘,对性能影响最小,数据安全性低,Redis 宕机丢失数据取决于操作系刷盘机制。

3 重写/压缩 AOF 文件

随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis 引入了 AOF 重写机制压缩文件体积。AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF文件的过程。

文件变小原因

  1. 进程内一超时的数据不再写入文件。
  2. 旧文件含有的无效命令,重写使用内存数据直接生成,这样新文件中只保留最终数据的写入命令。
  3. 多条写命令合并为一个。

触发方式

AOF 重写可以手动触发和自动触发:

  • 手动触发:直接调用 BGREWRITEAOF命令。

  • 自动触发:根据 auto-aof-rewrite-percentageauto-aof-rewrite-min-size 参数确定自动触发机制,当满足条件时,就会调用 Redis 的 BGREWRITEAOF 命令。

    // AOF文件距离上次文件增长超过多少百分比则触发重写
    auto-aof-rewrite-percentage 100
    // AOF文件体积最小多大以上才触发重写
    auto-aof-rewrite-min-size 64mb

重写机制

BGREWRITEAOF 命令 与 BGSAVE 命令相似,在执行 BGREWRITEAOF 命令后,父进程 调用 fork 创建一个子进程执行 AOF 的重写操作。流程如下所示:

在这里插入图片描述

流程说明:
1、执行 AOF 重写请求

2、父进程执行 fork 创建子进程

3.1、fork 完成后,主进程继续响应其他命令,所有修改命令写入 AOF 缓冲区

3.2、接收的写命令同时也写入 AOF 重写缓冲区中。

4、子进程根据内存快照,按照命令合并规则写入到新的 AOF 文件。

5.1、新 AOF文件写完成后,子进程发送信号给父进程。

5.2、父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件

5.3、使用新 AOF 文件替换老文件,完成 AOF 重写。

4 AOF 追加阻塞

当开启 AOF 持久化时,常用的同步硬盘策略是 everysec ,用于平衡性能和数据安全性。对于这种方式,Redis 使用另一个线程执行 fsync 同步刷盘。当系统硬盘资源繁忙是,会造成 Redis 主线程阻塞。

阻塞流程

  1. 主线程负责写入 AOF 缓冲区。
  2. AOF 线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间。
  3. 主线程负责对比上次 AOF 同步时间:
    • 如果距上次同步成功时间在 2 秒内,主线程直接返回。
    • 如果距上次同步成功时间超过 2 秒,主线程将会阻塞,直到同步操作完成。

阻塞流程发现问题

  1. everysec 配置最多可能丢失 2 秒数据,而不是 1 秒。
  2. 如果系统 fsync 缓慢,将会导致 Redis 主线程阻塞影响效率。

5 AOF 优缺点

AOF优点:

  1. AOF 可以更好的保护数据不丢失,一般 AOF 会以每隔 1 秒,通过后台的一个线程去执行一次 fsync 操作,如果 Redis 挂掉了,最多丢失 1 秒的数据。
  2. AOF 以 append-only 的模式写入,所以没有任何的磁盘寻址的开销,写入性能非常的高。
  3. AOF 日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用 flushall 命令清空了所有数据,只要这个时候还没有执行 Rewrite,那么就可以将日志文件中的flushall 删除,进行恢复。

AOF缺点:

  1. 对于同一份数据备份文件,AOF 比 RDB大
  2. AOF 开启后支持写的 QPS 会比 RDB 支持的写的 QPS 低,因为 AOF 一般会配置成每秒 fsync 操作,每秒的 fsync 操作还是很高的。
  3. 数据恢复比较慢,不适合做冷备。

参考资料:
《Redis 开发与运维》
《Redis 设计与实现》

你可能感兴趣的:(聊聊 Redis 高可用之持久化AOF和RDB分析)