深入剖析Redis高可用系列持久化-AOF和RDB

4次阅读

共计 6942 个字符,预计需要花费 18 分钟才能阅读完成。

欢迎关注公众号:「码农富哥」,致力于分享后端技术 (高并发架构,分布式集群系统,消息队列中间件,网络,微服务,Linux, TCP/IP, HTTP, MySQL, Redis), Python 等 原创干货 和 面试指南!

免费视频福利推荐:

2T 学习视频教程 + 电子书 免费送:BAT 面试精讲视频,亿级流量秒杀系统,分布式系统架构,中间件消息队列,Python Go 入门到精通,Java 实战项目,Linux,网络,MySQL 高性能,Redis 集群架构,大数据,架构师速成,微服务,容器化 Docker K8s,ELK Stack 日志系统等免费视频教程!

Redis 高可用概述

在介绍 Redis 高可用之前,先说明一下在 Redis 的语境中高可用的含义。

我们知道,在 web 服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999% 等等)。但是在 Redis 语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

在 Redis 中,实现高可用的技术主要包括持久化、复制、哨兵和集群,下面分别说明它们的作用,以及解决了什么样的问题。

  • 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  • 复制:复制是高可用 Redis 的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  • 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  • 集群:通过集群,Redis 解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

Redis 持久化概述

Redis 的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证 Redis 的数据不会因为故障而丢失,这种机制就是 Redis 的持久化机制。

Redis 为持久化提供了两种方式:

  • RDB:在指定的时间间隔能对你的数据进行快照存储。
  • AOF:记录每次对服务器写的操作, 当服务器重启的时候会重新执行这些命令来恢复原始的数据。

由于 AOF 持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此 AOF 是目前主流的持久化方式,不过 RDB 持久化仍然有其用武之地。

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

RDB 持久化

RDB 是默认的持久化方式,按照一定的策略周期性的将内存中的数据生成快照保存到磁盘。

每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。

1. 工作原理:

  • Redis 调用 fork(),产生一个子进程。
  • 子进程把数据写到一个临时的 RDB 文件。
  • 当子进程写完新的 RDB 文件后,把旧的 RDB 文件替换掉。

2. 触发机制

RDB 触发持久化分为手动触发和自动触发

  1. save 命令(手动触发)

当客户端向 Redis server 发送 save 命令请求进行持久化时,由于 Redis 是用一个主线程来处理所有,save 命令会阻塞 Redis server 处理其他客户端的请求,直到数据同步完成。save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在 Redis 服务器阻塞期间,服务器不能处理任何命令请求,因此线上环境不推荐使用

  1. bgsave 命令(手动触发)

与 save 命令不同,bgsave 是异步执行的,当执行 bgsave 命令之后,Redis 主进程会 fork 一个子进程将数据保存到 rdb 文件中,同步完数据之后,对原有文件进行替换,然后通知主进程表示同步完成。

  1. 自动触发

除了手动触发 RDB 持久化,Redis 内部还存在自动触发机制,

在配置中集中配置 save m n 的方式,表示 m 秒内数据集存在 n 次修改时,系统自动触发 bgsave 操作。

3. RDB 自动持久化配置

# 时间策略
save 900 1
save 300 10
save 60 10000

# 文件名称
dbfilename dump.rdb

# 文件保存路径
dir /etc/redis/data/

# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes

# 是否压缩
rdbcompression yes

# 导入时是否检查
rdbchecksum yes
rdb 持久化策略比较简单,下面解释一下:

save 900 1 表示 900s 内如果有 1 条是写入命令,就触发产生一次快照,可以理解为就进行一次备份
save 300 10  表示 300s 内有 10 条写入,就产生快照
下面的类似,那么为什么需要配置这么多条规则呢?因为 Redis 每个时段的读写请求肯定不是均衡的,为了平衡性能与数据安全,我们可以自由定制什么情况下触发备份。所以这里就是根据自身 Redis 写入情况来进行合理配置。

stop-writes-on-bgsave-error yes 这个配置也是非常重要的一项配置,这是当备份进程出错时,主进程就停止接受新的写入操作,是为了保护持久化的数据一致性问题。如果自己的业务有完善的监控系统,可以禁止此项配置,否则请开启。

rdbcompression yes 用于配置是否压缩 RDB 文件,建议没有必要开启,毕竟 Redis 本身就属于 CPU 密集型服务器,再开启压缩会带来更多的 CPU 消耗,相比硬盘成本,CPU 更值钱。

rdbchecksum yes 是否开启 RDB 文件的校验,在写入文件和读取文件时都起作用;关闭 checksum 在写入文件和启动文件时大约能带来 10% 的性能提升,但是数据损坏时无法发现

dbfilename dump.rdb RDB 文件名

dir ./ RDB 文件和 AOF 文件所在目录

当然如果你想要禁用 RDB 配置,也是非常容易的,只需要在 save 的最后一行写上:save “”

4. 执行流程图

1)  Redis 父进程首先判断:当前是否在执行 save,或 bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则 bgsave 命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。

2)  父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的,Redis 不能执行来自客户端的任何命令

3)  父进程 fork 后,bgsave 命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令

4)  子进程创建 RDB 文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换

5)  子进程发送信号给父进程表示完成,父进程更新统计信息

5. 数据恢复 & Redis 启动加载数据

RDB 文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于 AOF 的优先级更高,因此当 AOF 开启时,Redis 会优先载入 AOF 文件来恢复数据;

只有当 AOF 关闭时,才会在 Redis 服务器启动时检测 RDB 文件,并自动载入。服务器载入 RDB 文件期间处于阻塞状态,直到载入完成为止。

所以 Redis 的内存数据如果很大,会导致数据恢复时间比较长,因此线上实践更倾向于限制单个 Redis 的内存不能太大,同时结合 Redis Cluster 集群使用多节点部署

Redis 启动日志中可以看到自动载入的执行:

Redis 载入 RDB 文件时,会对 RDB 文件进行校验,如果文件损坏,则日志中会打印错误,Redis 启动失败。

大家如果更系统了解 Redis 高可用集群架构知识,可以关注公众号【码农富哥】后回复【Redis】获取 Redis 高可用集群架构视频

AOF 持久化

RDB 快照并不是很可靠。如果你的电脑突然宕机了,或者电源断了,又或者不小心杀掉了进程,那么最新的数据就会丢失。而 AOF 文件则提供了一种更为可靠的持久化方式。每当 Redis 接受到会修改数据集的命令时,就会把命令追加到 AOF 文件里,当你重启 Redis 时,AOF 里的命令会被重新执行一次,重建数据。

1. 工作原理

由于需要记录 Redis 的每条写命令,因此 AOF 不需要触发,AOF 的执行流程包括:

  • 命令追加(append):将 Redis 的写命令追加到缓冲区 aof_buf;
  • 文件写入 (write) 和文件同步(sync):根据不同的同步策略将 aof_buf 中的内容同步到硬盘;
  • 文件重写(rewrite):定期重写 AOF 文件,达到压缩的目的。

2. AOF 持久化配置

# 是否开启 aof
appendonly yes

# 文件名称
appendfilename "appendonly.aof"

# 同步方式
appendfsync everysec

# aof 重写期间是否同步
no-appendfsync-on-rewrite no

# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 加载 aof 时如果有错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes

3. AOF 同步策略

同步步骤分为两步:

  • Redis 收到写命令后首先会追加到 AOF 缓冲区 aof_buf,而不是直接写入文件系统,因为 AOF 缓冲区是内存提存的,写入速度极高,可以避免每次写入命令到硬盘,导致硬盘 IO 成为 Redis 的负载瓶颈
  • 通过调用系统函数 fsync() 把 AOF 缓冲区的数据真正写到磁盘里面持久化。由于数据是先存储在缓冲区内存里面,如果碰到断电,宕机那么缓冲区里面的数据没来得急落盘就会丢失,因此我们必须有一个相对可靠的机制保证数据落盘。

Redis 写命令写入磁盘的命令是通过 appendfsync 来配置的。
appendfsync 三个取值代表三种落盘策略:

  • always:命令写入 aof 缓冲区后立即调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回。这种情况下,每次有写命令都要同步到 AOF 文件,硬盘 IO 成为性能瓶颈。
  • no:命令写入 aof 缓冲区后调用系统 write 操作,不对 AOF 文件做 fsync 同步;同步由操作系统负责,通常同步周期为 30 秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
  • everysec:命令写入 aof 缓冲区后调用系统 write 操作,write 完成后线程返回;fsync 同步文件操作由专门的线程每秒调用一次。everysec 是前述两种策略的折中,是性能和数据安全性的平衡,因此是 Redis 的默认配置,也是我们推荐的配置。

4. AOF 文件重写(rewrite)

随着写操作的不断增加,AOF 文件会越来越大。例如你递增一个计数器 100 次,那么最终结果就是数据集里的计数器的值为最终的递增结果,但是 AOF 文件里却会把这 100 次操作完整的记录下来。而事实上要恢复这个记录,只需要 1 个命令就行了,也就是说 AOF 文件里那 100 条命令其实可以精简为 1 条。所以 Redis 支持这样一个功能:在不中断服务的情况下在后台重建 AOF 文件。

AOF 重写流程:

关于文件重写的流程,有两点需要特别注意:

(1)重写由父进程 fork 子进程进行;

(2)重写期间 Redis 执行的写命令,需要追加到新的 AOF 文件中,为此 Redis 引入了 aof_rewrite_buf 缓存。

对照上图,文件重写的流程如下:

1) Redis 父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof 的子进程,如果存在则 bgrewriteaof 命令直接返回,如果存在 bgsave 命令则等 bgsave 执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。

2) 父进程执行 fork 操作创建子进程,这个过程中父进程是阻塞的。

3.1) 父进程 fork 后,bgrewriteaof 命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis 的所有写命令依然写入 AOF 缓冲区,并根据 appendfsync 策略同步到硬盘,保证原有 AOF 机制的正确。

3.2) 由于 fork 操作使用写时复制技术,子进程只能共享 fork 操作时的内存数据。由于父进程依然在响应命令,因此 Redis 使用 AOF 重写缓冲区 (图中的 aof_rewrite_buf) 保存这部分数据,防止新 AOF 文件生成期间丢失这部分数据。也就是说,bgrewriteaof 执行期间,Redis 的写命令同时追加到 aof_buf 和 aof_rewirte_buf 两个缓冲区。

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

5.1) 子进程写完新的 AOF 文件后,向父进程发信号,父进程更新统计信息,具体可以通过 info persistence 查看。

5.2) 父进程把 AOF 重写缓冲区的数据写入到新的 AOF 文件,这样就保证了新 AOF 文件所保存的数据库状态和服务器当前状态一致。

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

重写触发:

  1. 手动触发:直接调用 bgrewriteaof 命令,该命令的执行与 bgsave 有些类似:都是 fork 子进程进行具体的工作,且都只有在 fork 时阻塞。
  2. 自动触发:通过配置 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size来完成

auto-aof-rewrite-percentage 100:Redis 会记住自从上一次重写后 AOF 文件的大小(如果自 Redis 启动后还没重写过,则记住启动时使用的 AOF 文件的大小)。如果当前的文件大小比起记住的那个大小超过指定的百分比,则会触配置发重写。

auto-aof-rewrite-min-size 64mb:同时需要设置一个文件大小最小值,只有大于这个值文件才会重写,以防文件很小,但是已经达到百分比的情况。

要禁用自动的日志重写功能,我们可以把百分比设置为 0:

auto-aof-rewrite-percentage 0:禁用日志重写功能

5. 数据恢复 & Redis 启动加载数据

前面提到过,当 AOF 开启时,Redis 启动时会优先载入 AOF 文件来恢复数据;

只有当 AOF 关闭时,才会载入 RDB 文件恢复数据。

当 AOF 开启,且 AOF 文件存在时,Redis 启动日志:

持久化方案选择

1. RDB 和 AOF 的优缺点

RDB 和 AOF 各有优缺点:

RDB 持久化

  • 优点:RDB 文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比 AOF 快很多。当然,与 AOF 相比,RDB 最重要的优点之一是对性能的影响相对较小。
  • 缺点:RDB 文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此 AOF 持久化成为主流。此外,RDB 文件需要满足特定格式,兼容性差(如老版本的 Redis 不兼容新版本的 RDB 文件)。

AOF 持久化

  • 与 RDB 持久化相对应,AOF 的优点在于支持秒级持久化、兼容性好,缺点是文件大、恢复速度慢、对性能影响大。

2. 性能与实践

通过上面的分析,我们都知道 RDB 的快照、AOF 的重写都需要 fork,这是一个重量级操作,会对 Redis 造成阻塞。因此为了不影响 Redis 主进程响应,我们需要尽可能降低阻塞。

  • 降低 fork 的频率,比如可以手动来触发 RDB 生成快照、与 AOF 重写;
  • 控制 Redis 最大使用内存,防止 fork 耗时过长;
  • 使用更牛逼的硬件;
  • 合理配置 Linux 的内存分配策略,避免因为物理内存不足导致 fork 失败。

在线上我们到底该怎么做?我提供一些自己的实践经验。

  • 如果 Redis 中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回;
  • 自己制定策略定期检查 Redis 的情况,然后可以手动触发备份、重写数据;
  • 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO 资源竞争,让持久化变为串行;
  • 可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令;
  • RDB 持久化与 AOF 持久化可以同时存在,配合使用。

总结

Redis 的高可用系列:持久化就已经讲完了,持久化主要有 RDB 和 AOF 两种技术,大家按照上面所介绍的原理和流程,根据线上具体需求选择适合自己的持久化方案。

另外,写原创技术文章不易,要花费好多时间和精力,希望大家看到文章也能有所收获!你们的点赞和收藏就能成为我继续坚持输出原创文章的动力!大家也可以关注我的公众号,订阅更多我的文章!

欢迎关注公众号:「码农富哥 」,致力于分享后端技术 (高并发架构,分布式集群系统,消息队列中间件,网络,微服务,Linux, TCP/IP, HTTP, MySQL, Redis), Python 等 原创干货 和 面试指南!
关注公众号后回复【资源】免费获取 2T 编程视频和电子书,回复【Redis】获取 Redis 高可用集群架构视频

正文完
 0