redis 指南(5): 未雨绸缪 | 图解深入 RDB 与 AOF
这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战
前言
Redis 它是一个键值对的内存数据库,读写数据都是基于内存的,所以它的性能非常高,但同时如果服务器一旦宕机,那么内存的数据是不可恢复的,所以,redis 想到了持久化,如何把内存中的数据优雅的同步到磁盘中,以便 redis 在重启时能够恢复原有的数据,这就是持久化。
Redis 的持久化有三种方式:
- RDB 快照 :将数据库的某个状态的内存数据,以二进制的方式写入磁盘中。
- AOF 文件追加(Append Only File):记录所有的操作命令,并以文本的形式追加到文件中
- 混合持久化方式:这是 Redis4.0 之后的功能,结合了 RDB 和 AOF 的优点。用AOF来保证数据不丢失,作为数据恢复的第一选择;用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复
这三种使用方式看具体的应用场景,而用到的 RDB 和 AOF 这两种持久化模式是我们需要掌握的,这篇文章将对 RDB 与 AOF 将进行详细解读。
一、RDB 概述
RDB 是 Redis DataBase 的简称,它是将某个时刻的内存快照(Snapshot),以二进制的方式写入到磁盘中。那么如何将数据写入到磁盘中呢,如何触发 RDB 的持久化呢,这里是分为手动保存和自动保存两种模式。
1.1 手动保存
执行 RDB 持久化的操作命令有两个,save 和 bgsave。
- SAVE 命令会直接阻塞服务器进程,直到 RDB 文件创建完毕,在此期间,服务器不能处理任何命令请求!!
- bgsave 命令会派生处一个子进程,然后子进程去创建 RDB 文件,服务器进程继续处理命令请求,
SAVE 演示:
首先查看 dump.rdb 文件的时间为 7月份,这是,我们使用 save 命令,发现 RDB 文件已经更新,说明此时 save 命令成功触发了 redis 的持久化。save 命令执行的时候会阻塞 Redis 服务器,所以这种命令还是非常危险的。
BGSAVE 演示:
BGSAVE 命令的执行如图所示。它的保存工作是由子进程执行的,所以子进程在保存的同时,Redis 服务器仍然可以处理客户端的命令请求,同时,BGSAVE 命令在执行期间,save 命令和 bgsave 命令 都不能同时执行。
1.2 自动保存
刚才我们说完了 手动保存的两个命令,两个命令的主要区别就是 :
- save 命令是由服务器进程执行保存工作的,会阻塞服务器
- bgsave 命令是由子进程执行保存工作的,不会阻塞服务求。
因此,我们默认用的就是 bgsave 命令,在redis 的配置中 有个 save 选项,可以实现自动间隔保存,此外,在执行 flushall、主从同步的时候也会促发自动保存。
1.2.1 save
Redis 的配置中有个 save 命令,注意的是,可能有些读者会认为这 save 与前面的 save 是一个命令,但它实际上执行的是 bgsave。是要创建子进程去保存 RDB 文件的。
触发的条件如下,注意的是 Redis 的快照是全量快照,每一次触发的时候要把当前的内存数据都记录到磁盘中,如果在数据量很大的时候频繁执行是会对 Redis 的性能产生影响的。
save 900 1:表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10:表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000:表示60 秒内如果至少有 10000 个 key 的值变化,则保存
1.2.2 flushall 命令
这个命令用来清空数据库,如果执行了,会生成 dump.rdb 文件
1.2.3 退出触发
退出 redis 的时候也会触发持久化,生成 dump.rdb 文件
1.2.4 主从同步触发
在 Redis 主从复制中,当从节点执行全量复制操作时,主节点会执行 bgsave
命令,并将 RDB 文件发送给从节点,该过程会自动触发 Redis 持久化。
1.3 数据恢复
Redis 载入内存数据的工作是在启动的时候执行的,只要 Redis 服务器在启动时候检测到启动目录时有 RDB 文件存在,就会自动载入 RDB 文件。如果启动根目录没有 dump.rdb 文件,请先将 dump.rdb 文件移动到 Redis 的根目录。 验证 RDB 文件是否被加载 Redis 在启动时有日志信息,会显示是否加载了 RDB 文件,
1.4 COW 机制
要理解 RDB,就不得不提 操作系统的 COW 机制。这里提出一个问题,RDB 文件是由子进程去负责写入数据的,加入这个时候 RDB准备去持久化 数据 A的值为 1 ,但在这之前,主线程的请求把 数据 A 的值改成 2了,这时持久化 A 的时候写入 RDB 文件的是 1还是 2 呢?结论就是数据能被修改,但是写入 RDB 的数据是 1 ,是修改之前的数据。
下面我们来分析下这个过程:
执行 fork 创建子进程的时候,此时 子进程和父进程是共享一片内存数据的,子进程通过负责父进程的页表,引用的物理内存和父进程一样。那么这时候如果父类修改页表的数据,那么子进程读取的数据不就发生变化了吗?
其实并不是这样的,这里我们就需要讲下 COW 了。 Redis 使用操作系统的多进程 COW (copy on write)机制来实现快照持久化。这个有什么好处呢?
所谓 COW 机制,指的是修改共享资源时,首先将共享资源 copy 一份,加锁后修改,再将原容器的引用指向新的容器,这样每个进程之间操作的内存就不是同一个,不会产生数据问题了。此时的指向如下:
所以 子进程是可以保存原来的数据的,而此时的内存中 A 已经是2了,这值只能由下次的 RDB 触发的时候写入了。
以上就是 COW 的图形展示,缺点也有,COW 它会将内存 copy 一份,如果所有都改变的化,那所有的内存就需要 copy 一份,相当于内存减半,需要足够的内存才可以实现 COW。因此,快照,我们可以再做个定义,Redis 在 进程 RDB 触发的那一时刻,保存的就是那一时刻的数据,尽管那些数据在之后可能会改变,但是我保存的是 快照,时间定格在那一刻的数据,这就是快照的理解。
COW 机制总结:
fork一个子进程,只有在父进程发生写操作修改内存数据时,才会真正去分配内存空间,并复制内存数据,而且也只是复制被修改的内存页中的数据,并不是全部内存数据.
1.5 优缺点
优点:
- RDB非常适合做冷备,占用内存小,对容灾恢复比较有用。
- RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可
- 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速
缺点:
- RDB 容易丢失数据,一旦宕机,最近几分钟的数据都没了
- RDB 在fork 子进程生成大文件数据的时候,可能会导致redis 性能下降,造成服务器短暂暂停提供服务,一般为几毫米。
二、AOF 概述
除了 RDB 持久化功能之外,Redis 还提供了 AOF持久化功能。与 RDB 快照不同,它不是保存那一时刻的数据,而是把所有的命令追加到 AOF 文件中,服务器在启动的时候可以通过载入和执行 AOF 文件中保存的命令来还原服务器关闭之前的数据库状态。
Redis 默认不开启 AOF 持久化方式,可通过配置文件更改。
但此时还不能生效,还需要把 RDB 的save 命令注释掉,设置 save 为 “” ,然后重启 redis 测试即可
随便输入几个命令 ,打开 appendonly 文件,AOF 文件保存的就是我们操作redis 的命令。
2.1 持久化实现
AOF 的持久化实现可以分为命令追加(append)、文件写入(write)、文件同步(sync) 三个步骤。
2.1.1 命令追加
当 redis 使用 AOF 持久化的时候,服务器在执行完一个写命令之后,会以协议格式将执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。
2.1.2 文件的写入和同步
为了提高文件的写入效率,在现代操作系统中,当用户调用 write 函数,将一些操作写入文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满,或者超过指定的时限之后,才真正的将缓冲区的数据写入到磁盘里面,例如下面这个流程图:
加入缓冲区提高了写入的效率,但是也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的数据将会丢失,因此,操作系统提供了 fsync 和 fdatasync 两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘,从而保证安全。
对应正常情况下的同步数据是由 redis.conf 的 appendfsync 的设置策略来的,它决定了 AOF 持久化功能的效率和安全。
appendfsync 选项的值 | 执行功能 |
---|---|
always | 客户端的每一个写操作都保存到 aof 文件中,这种策略很安全,但是每个写请求都有 IO 操作,所以很慢。 |
everysec | 默认的写入策略,每秒写入一次 aof 文件,因此,最多可能丢失 1s 的数据 |
no | Redis 服务器不负责写入 aof ,而是给操作系统来处理什么时候写入 aof 文件,更快,但是不安全。 |
2.2 载入 AOF 文件(load)
前面也看了 AOF 的文件内容,它记录着重建数据库状态所需的所有写命令,所以服务器只要读入 AOF 文件把里面的命令重新执行一边就可以还原数据库的状态了。
Redis 载入 AOF 文件并还原数据库状态的详细步骤如下:
1、创建一个不带网络连接的伪客户端:因为 Redis 的命令只能在客户端执行,所以创建了个不带网络连接的伪客户端执行本地的命令请求写入 AOF 文件到内存
2、从 AOF 文件中分析并读取出一条写命令
3、使用伪客户端执行被读出的写命令
4、重复执行,知道 AFO 的所有写命令都执行结束
2.3 AOF 重写(rewrite)
Redis 是先执行写命令然后把这个命令写入 AOF 缓冲区的。但在 Redis 长期运行的过程中,AOF 的日志会越来越长。如果宕机重启之后,重写载入 AOF
文件的写命令操作将会非常耗时,导致 Redis 在长时间无法对外提供服务,所以需要对 AOF 日志进行瘦身。所谓瘦身就是对命令的重写整理,让它变的别那么臃肿。
2.3.1 AOF 文件重写的实现
为了解决 AOF 文件变的臃肿的问题,Redis 可以创建一个新的 AOF 文件来替换现有的 AOF 文件这个过程叫做 AOF 文件重写。
但是,AOF 文件重写并不需要对现有的 AOF 文件进行读取、分析和写入操作,而是根据当前数据库状态来实现的。
例如,原先的 AOF 文件命令可以能是这样的,需要把这十条命令都保存下来。
新的 AOF 文件不管老的 AOF 文件,它只需去当前数据库中读取到 当前的键的状态,例如当前的 list 键下有五个元素,它就可以写成一句:
rpush list a b c d e
而对五条 set 命令,只需获取到当前 k1 的值为 v5,即把这条命令复制到 新的 AOF 中,前面的废弃命令就不用管了。
这就是 AOF 重写瘦身是实现,它是针对当前数据库状态,对命令瘦身来的。
2.3.2 AOF 后台重写
前面的 RDB 写入数据的时候 通过 bgsave 来实现后台重写,而 AOF 也是需要进行后台重写的。
同样 Redis 在重写程序的时候会将它放入到子进程中执行,这样子进程进行 AOF 重写期间,Redis 进程可以继续处理客户端命令请求。但是一样也会遇到数据不一致问题,子进程在重写的过程中,数据发生了改变,造成数据库当前状态和重写的 AOF 文件中的状态不一致怎么办?
为了解决这个问题,Redis 服务器设置了一个 AOF 重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当 Redis 服务器执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。
为什么要分开呢?因为 AOF 缓冲区记录的是所有命令,而 AOF 重写缓冲区是创建子进程后服务器执行的写命令,内容还是不一样的。
在子进程完成 AOF 文件重写之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:
- 1、将 AOF 重写缓冲区中的所有内容写入新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
- 2、对新的 AOF 文件进行改名,覆盖原有的 AOF 文件,完成替换
- 3、继续处理客户端请求命令。
怎么说呢,重写的前提是为了保证数据库当前状态和 AOF 文件的数据一致,所以这里在创建子进程进行重写之后,会把后续的命令放入缓冲区中,尽管这些命令可能会重复,例如 set k1 v1 ;set k1 v2;但是为了保证一致性,在 新的 AOF 文件重写之后,需要把这些命令追加到文件的末尾,尽管它不符合精简的要求,但是数据一致性第一。
这就是 AOF 的后台重写,还是比较有意思的吧。
2.4 优缺点
优点:
AOF 只是追加日志文件,因此对服务器性能影响较小,速度比 RDB 要快,消耗的内存较少
缺点:
- 1、AOF 方式生成的日志文件太大,即时通过 AFO 重写,文件的体积仍然很大
- 2、重启载入恢复数据比 RDB 慢。
三、如何选择
重启 Redis 时,我们很少使用 RDB 来恢复内存状态,因为会丢失大量数据。我们通常 使用 AOF 日志载入,但是载入 AOF 日志的过程相对 RDB 要慢很多,那么我们应该怎么选择呢?
官网的建议是:
- 如果你只做缓存的话,就可以不适用持久化
- 如果能承受数分钟内的数据丢失,那可以只是用 RDB 持久化
- Redis 重启会优先载入 AOF 文件来恢复,因为 RDB 更适合做备份数据库,快速重启,没有 AOF 潜在的 bug。
但是感觉说了等于没说,它并没有解决我们之前的问题,但是 Redis4.0 之后提供了一个新的持久化选项---混合持久化,它将 RDB 文件的内容和增量的 AOF 日志文件存放在一起。这里的 AOF 日志不再是 全量的日志,而是 自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分日志很小。
因此,采用这种模式就与官方的建议不一样了,它对它进行了优化。在 Redis 重启的时候,先加载之前的 RDB 文件的内容,然后再重放增量的 AOF 日志,就可以优化前面的 AOF 全量载入了,重启的效率大大的提升。
混合持久化配置如下:
aof-use-rdb-preamble yes # yes:开启,no:关闭
以上就是我对 redis 的 AOF 与 RDB 内容的理解,本文耗时 5 小时左右的学习与编写,全手打和绘图,有不对的地方欢迎评论讨论一起交流,也欢迎大家能点赞收藏。
参考资料
- 《redis 深度历险》
- 《Redis 设计与实现》
转载自:https://juejin.cn/post/7000536070936854541