深入理解 RDB 与 AOF 持久化机制
Hello , 大家好 , 这个专栏给大家带来的是 Redis 系列 ! 本篇文章给大家讲解的是 Redis 的持久化 . 在 Redis 中 , 分为 RDB 和 AOF 两种持久化方式 , 那我们就研究一下 RDB 和 AOF 之间具体的差别与关联 .
本专栏旨在为初学者提供一个全面的 Redis 学习路径,从基础概念到实际应用,帮助读者快速掌握 Redis 的使用和管理技巧。通过本专栏的学习,能够构建坚实的 Redis 知识基础,并能够在实际学习以及工作中灵活运用 Redis 解决问题 .
专栏地址 : Redis 入门实践
一 . 认识持久化
我们之前就接触过持久化这个概念 , 在 MySQL 的事务中 , 有四个重要的特性
- 原子性
- 一致性
- 持久性 : 跟持久化是一个概念
- 隔离性
在 MySQL 中是通过将数据存储到硬盘中来达到持久性 .
那所谓的持久性 , 我们可以这样理解
- 把数据存放到硬盘上 , 是持久的
- 把数据存放到内存中 , 是不持久的
通过重启进程 / 重启主机之后 , 如果数据依然存在 , 那么他就是持久的 .
而 Redis 是一个内存型数据库 , 他是把数据放到内存中的 , 但是内存中的数据并不是持久的 , 我们要想做到持久 , 就需要把 Redis 中的数据存放到硬盘上 .
那 Redis 就遇到了一个两难的问题 :
- 为了保证数据快 , 我们还需要把数据存放到内存中 ;
- 为了保证数据不丢失 , 数据还需要存放到硬盘中 .
那 Redis 采取的策略就是将数据都存放到内存和硬盘中 , 并且这两份数据理论上是完全相同的 .
那当我们插入一条新的数据 , 就需要将这份数据同时写入到内存和硬盘中 , 但是读取这份数据只需要直接从内存读取 . 而硬盘中的数据只有在 Redis 重启的时候 , 才需要将内存中的数据恢复到内存中 (就相当于一个备份)
在 Redis 中 , 实现持久化是按照这两种策略来实现持久化的
- RDB : Redis DataBase
- AOF : Append Only File
我们分别来看 RDB 和 AOF 这两种机制
二 . RDB
2.1 RDB 的触发时期
RDB 会定期的把我们 Redis 内存中的所有数据 , 都写入到硬盘中 , 然后生成一个快照
快照指的就是我们拍的照片 , 根据拍的照片还原出当时发生了一些什么 , 可以理解为还原犯罪现场的工具
那 Redis 就会给内存中存储的数据拍个照片 , 生成一个文件存储到硬盘中 , 也就是快照 .
后续 Redis 一旦重启了 , 也就是内存中存放的数据就被清空重置了 , 就可以根据存储的快照来去将数据恢复到内存中 .
那针对于 “定期” , 又有两种情况 :
- 手动触发 : 程序员通过 Redis 的客户端 , 执行特定命令 , 来触发快照的生成
- save : 执行 save 的时候 , Redis 就会全力以赴的进行 “快照生成” 工作 , 那就会阻塞 Redis 其他客户端的命令 , 导致 Redis 不能正常工作 , 进一步导致系统不能正常工作 ; 一般情况下我们并不建议使用 save 操作
- bgsave : backgroundsave , 指的是在背后进行保存 , 那也就代表 bgsave 并不会影响 Redis 主服务器处理其他客户端的请求和命令
那 Redis 是怎样做到 bgsave 的 , 是不是暗地里还是偷偷使用了多线程呢 ?
并不是 , 我们要知道 , 实现并发编程有很多方案 , 但是多线程只是其中一种 , 并不是只有多线程才能实现并发 . 而 Redis 使用的是多进程的方式来去实现的并发编程 , 从而完成的 bgsave 的实现
- 自动触发 : 在 Redis 的配置文件中设置让 Redis 每隔多长时间 / 每产生多少次修改 就触发持久化操作
2.2 RDB 的 bgsave 执行流程
那如果子进程是首次写文件 , 那还好 , 直接创建出一个空文件即可 ; 那如果子进程不是首次写入文件 , 那是不断创建出新的文件还是在之前的文件中追加内容呢 ?
我们通过模拟 , 可以观察 RDB 的镜像文件来去总结
2.3 RDB 的 rdb 镜像文件
Redis 生成的 rdb 文件 , 是存放在 Redis 的工作目录中的
我们可以在 Redis 的配置文件中进行设置 , 通过 vim /etc/redis/redis.conf 打开配置文件
这个选项就代表 rdb 文件的存储路径
在这个路径下面的 dump.rdb 就是我们的 RDB 机制生成的镜像文件了
这个文件是一个二进制文件 , Redis 会将内存中的数据压缩到二进制文件中
我们尝试打开 , 也会发现一些熟悉的面庞
接下来我们可以来具体看一下 RDB 执行的流程
2.4 RDB 效果演示
我们开启两个终端 , 一个打开 Redis , 一个找到 dump.rdb 文件
dump.rdb 存放在 /var/lib/redis
然后在最刚开始 , 清空一下数据库 , 然后去观察一下 dump.rdb 文件
然后插入一些数据之后 , 再去观察一下 dump.rdb 文件
我们发现并没有什么变化 , 这是因为我们在最刚开始就给大家讲过
RDB 的触发时机 :
- 手动触发 : 执行 save / bgsave 命令
- 自动触发 : 在配置文件中进行设置
那我们刚才只是普通的插入几个键值对 , 并不能满足触发条件 .
那这个自动触发中 , 什么情况下才能自动触发 RDB 呢 ? 我们也可以在 Redis 的配置文件中找到
虽然这些数据能够手动配置 , 但是要有一个基本原则 : 生成一次 RDB 快照 , 是一个成本比较高的操作 , 不能够频繁地触发 RDB 操作 , 而且我们也并不推荐大家随便修改配置文件 .
正因为 RDB 生成的不能过于频繁 , 这就导致快照中的数据和当时实际的数据情况可能存在一定偏差 , 比如 : 现在是 12:00 , 我们的机制是在 15min 之后才能触发 RDB 操作 , 但是恰恰在这个时间段 Redis 服务器宕机了 , 那恢复数据只能恢复 12:00 之前的数据 , 12:00 之后的数据就丢失了
那我们接下来模拟一下几种不同的情况
手动执行 save / bgsave 触发一次生成快照
在手动执行 bgsave 之后 , 我们隐隐约约就可以看到一些刚才存储的一些的信息了
那我们强制重启一下服务器 : service redis-server stop
然后看一下服务是否已经停掉了 : ps aux | grep redis-server
然后再重新启动 Redis 的服务器 : service redis-server start
再去重新启动 Redis 的客户端 : redis-cli
我们就可以获取到之前的数据了
插入新的 key , 但是不手动执行 bgsave
我们先来看一下目前已经存在的键值对
我们也去看一下 RDB 文件 : vim /var/lib/redis/dump.rdb
RDB 文件现在是这样
那我们新添加一个键值对 : k4
然后我们重新查看一下 dump.rdb
然后此时 , 我们不执行 bgsave 操作 , 我们直接重启 Redis 服务器
使用命令 : service redis-server restart
那按照正常情况 , 我们并没有达到生成快照的要求 , 所以重启 Redis 服务器 , k4 这个键值对就会消失
但是事情是这样吗 ?
Redis 生成快照的操作 , 不仅仅是手动执行命令才会触发 , 他也可以自动触发 .
我们刚才介绍了 , 通过配置文件中设置的多少时间内修改多少次来达到自动触发 , 这是一种方式 ;
那还有两种情况 , 也会自动生成 RDB 快照
- 通过 shutdown 命令关闭 Redis 服务器 , 也会触发 RDB 操作 (就是 service redis-server restart 命令)
- [后续介绍] Redis 在进行主从复制的时候 , 主节点也会自动生成 RDB 快照 , 然后将 RDB 快照文件的内容传输给从节点
那我们讨论的情况 , 是插入新的 key , 但是不手动执行 bgsave , 我们的想法是这个新的 key 就丢失了 , 但是刚才介绍的三种情况 , 并不会让新的 key 丢失 . 那我们刚才介绍的都是理想情况 , 如果 Redis 服务器突然宕机 / 突然掉电 … 情况 , 数据就不一定保得住了
我们可以模拟一下突发情况
先设置一个新的键值对
然后通过 kill 命令以极快的速度杀死 Redis 进程 , 这样 Redis 进程反应不过来就不会触发生成快照操作
此时继续连接到 Redis 服务器
通过配置自动生成 RDB 快照
我们只需要修改 Redis 的配置文件 , 就可以通过配置自动生成 RDB 快照
我们将默认的配置注释掉 , 然后自己添加一个配置 : save 60 2 , 代表 60s 内修改两次数据 , 就自动生成快照
然后保存配置文件
按住 Esc , 然后输入 :wq 即可
重启 Redis 服务器
使用命令 service redis-server restart
然后我们先清空一下数据库 , 然后构造超过两组的数据
等待 60s 之后 , 我们去看一下 dump.rdb 文件
要注意的是 , 有一个特殊操作 , 当我们配置 save “” 这样的空字符串的时候 , 就代表我们关闭自动生成快照 . 那 Redis 就纯纯的变成了一个真正的内存数据库了 .
如果故意改坏 RDB 文件
我们打开 dump.rdb 这个文件 : vim /var/lib/redis/dump.rdb
然后使用 kill -9 进程 ID 这个命令来去杀死进程
注意 : 不能使用 service redis-server restart 这个命令进行重启 , 这个命令会在 Redis 服务器退出的时候重新生成 RDB 快照 , 就会将我们故意修改的 RDB 文件覆盖掉
然后我们再去查看一下之前修改过的 dump.rdb 文件
那我们再连接 Redis 客户端 , 查看一下能否正常访问之前的键值对
那看起来没毛病不代表真的没毛病 , 只不过使我们修改的轻了 . 如果改坏的位置是末尾 , 那就对前面的内容没什么影响 ; 但是如果改坏的位置是中间 , 这就说不准了
按 Esc 键 , 然后输入 :wq 进行保存
此时我们再去杀死 Redis 服务器进程 , 使用 kill -9 进程 ID 命令
那我们手动启动 Redis 呢 , 使用 service redis-server start 来去启动 Redis
完喽 , 搞坏了~
我们可以通过 Redis 的日志来看一下错误原因 , Redis 的日志存放在 /var/log/redis 中 , 使用 cd /var/log/redis 来进入到该目录
红色字体的都是对日志的压缩包
使用 vim redis-server.log 来去查看日志
在最下面就打印出了错误日志
那我们也看出来了 , RDB 文件是二进制的 , 如果我们把坏了的 RDB 文件交给 Redis 服务器来去使用 , 那得到的结果是不可预期的 , 有可能服务器能够启动但是获取到的结果可能存在问题 , 但是也有可能 Redis 服务器直接启动失败 .
那 Redis 也料想到了 , 就提供了 RDB 文件的检查工具 , 我们可以先通过检查工具来去检查一下 RDB 文件的格式是否符合要求
首先进入到 /uer/bin 目录 , 使用 cd /uer/bin 命令
然后列举出 Redis 一系列的工具 , 使用 ll redis-* 来去查看
我们可以使用一下这个工具 , 使用命令 redis-check-rdb dump.rdb 就可以进行检查
2.5 bgsave 底层细节
当执行生成 RDB 镜像操作的时候 , bgsave 就会创建出一个子进程 , 通过子进程来完成持久化操作 .
此时就会把快照数据先保存到一个临时文件中 , 当快照生成完毕之后 , 再删除之前的 RDB 文件 , 把新生成的临时的 RDB 文件名字修改成刚才的 dump.rdb 来达到生成 RDB 的镜像操作 .
子进程他操作的速度太快 , 我们不便观察 . 但是我们可以观察一下新的文件替换旧的文件这个操作
我们可以通过 Linux 的 stat 命令 , 查看文件的 inode 编号 . 如果编号发生变化 , 那就代表不是同一个文件
那在执行 bgsave 之前 , 先看一下 dump.rdb 文件的 inode 编号
然后执行 bgsave 操作
然后再查看一下 dump.rdb 文件的 inode 编号
编号发生了变化 , 这就代表文件不再是同一个文件了
如果使用的是 save 命令 , 那就不会触发生成子进程然后替换文件的逻辑 , 只是让主进程来去完成替换文件的逻辑
三 . AOF
AOF 的全称是 append only file , 意思就是在文件后面追加
他就类似于 MySQL 中的 binlog , 将用户的每个操作都记录到文件中 . 当 Redis 重新启动的时候 , 就会读取 AOF 文件中的内容 , 用来恢复数据 .
❓ 那 Redis 服务启动的时候 , 到底是读取 RDB 还是读取 AOF 呢 ?
✔️ Redis 会优先读取 AOF , 当我们开启 AOF 的时候 , RDB 就会失效 , 就不会在读取 dump.rdb 文件的内容了
3.1 AOF 的基本使用
AOF 默认是关闭状态 , 我们需要修改配置文件然后开启 AOF 功能
打开 Redis 的配置文件 : vim /etc/redis/redis.conf
然后在下面输入 /appendonly 就可以进行搜索
我们需要修改这个配置为 yes
AOF 文件所在的位置 , 跟 RDB 文件所在的位置一样 , 都是在 /var/lib/redis 目录中
设置结束之后我们重新开启一下服务器 : service redis-server restart
既然能够启动 , 就代表我们现在读取的不是 dump.rdb 了 , 而是 appendonly.aof 文件了
因为我们之前手动的把 dump.rdb文件修改坏了导致 Redis 服务器启动失败
那我们也可以看一下 /var/lib/redis 目录中是否出现了 appendonly.aof 文件 , 使用 cd /var/lib/redis 就可以跳转到对应目录
其实我们现在已经可以把 dump.rdb 文件删除掉了 , 因为接下来都是读取的 appendonly.aof 文件了
使用命令 : rm -rf dump.rdb 即可删除
AOF 持久化的策略是每一次操作都会写入到 appendonly.aof 文件中
可以看出 , AOF 是一个文本文件 , 每次执行的操作都会保存到 appendonly.aof 这个文本文件中 , 然后通过一些特殊的符号作为分隔符 , 对各个命令进行区分 .
那此时 , 如果我们重启 Redis 服务器 , 也是能够正常读取 appendonly.aof 中的内容的
通过 ps -aux | grep redis 来去查找 Redis 服务的进程 ID , 通过 kill -9 进程 ID 来去杀死对应的进程
此时我们再去获取之前设置的数据也是没问题的
3.2 AOF 是否会影响到 Redis 的性能 ?
我们刚才也了解到了 , AOF 是一个文本文件 . 那相比于 RDB , AOF 又要写内存 , 又要写硬盘 , 这不是拖慢了速度吗 ?
但是实际上 , 是并没有影响的 , 他并没有影响到 Redis 处理请求的速度
- AOF 机制并不是直接让工作线程把数据写入到磁盘中 , 而是先写入到内存中的一个缓冲区 , 然后一定程度之后再写入到硬盘 , 这样就大大的减少了写硬盘的次数
- 在硬盘上读写数据也不都是很慢的 , 一般来说顺序读写的速度也是比较快的 (但是仍然比内存慢) , 但是随机访问速度就比较慢了 . 那我们的 AOF 是顺序读写 , 也不算太慢 .
3.3 AOF 缓冲区的刷新策略
我们刚才介绍了 , AOF 存储数据的策略是先把一系列的数据存储到内存中的一个缓冲区中 , 等待一定程度之后再写入到磁盘中 .
那就有一种可能 , 如果我们把数据写入到缓冲区中 , 他本质还是存放到内存中啊 . 如果突然 Redis 进程挂了或者主机掉电了 , 怎么办啊 ? 缓冲区的数据还能保得住吗 ?
很遗憾 , 缓冲区的数据如果没写入到 appendonly.aof 中 , 那就找不到了 .
那 Redis 还做不到既快又能持久化这样的特点 , 但是 Redis 给出了一些选项 , 可以让程序员根据实际情况来去设置缓冲区的刷新策略 .
刷新频率越高 , 性能影响就越大 , 同时数据的可靠性就越高 ; 刷新频率越低 , 性能影响就越小 , 数据的可靠性就越低 .
Redis 提供了三个级别的刷新策略
选项 | 刷新策略 | 邓丽 |
---|---|---|
always | 每执行一个写命令就保存一次 | 刷新频率最高 , 数据可靠性最高 , 性能最低 |
everysec | 每秒保存一次 | 刷新频率降低 , 数据可靠性降低 , 性能会提高 |
no | 操作系统决定刷新频率 | 刷新频率最低 , 数据可靠性最低 , 性能最高 |
我们 Redis 默认的策略是每秒刷新一次缓冲区
3.4 AOF 的重写机制
随着命令的不断追加 , AOF 文件体积也会持续增长 , 而 Redis 在启动的时候会去读取 AOF 文件的内容 , 那就会影响到 Redis 下次启动的启动时间 .
并且 AOF 中的文件也有可能是冗余的 , 比如这样的一组命令
set key 111
set key 222
set key 333
实际上最后的结果就是 set key 333
再或者说
set key 111
del key
set key 222
del key
那这组命令就等价于什么都不做
我们也可以看到 , AOF 文件记录了中间的过程 , 但是实际上 Redis 在重新启动的时候 , 只需要关注最终的结果 .
因此 Redis 就提供了一个机制 , 能够对 AOF 文件进行整理 , 将冗余的操作剔除掉 , 达到给 AOF 文件瘦身的效果 , 这个机制就是 AOF 重写机制 .
比如我们的记账软件 , 我们不关心钱都花在哪了 , 或者有多少钱进账了 , 我们只关心余额剩多少 , 中间反反复复的运算我们并不关心 .
3.4.1 触发时机
AOF 的重写过程也分为两种 :
- 手动触发 : 执行 bgrewriteaof 命令
- 自动触发 : 根据配置文件中 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 来确定自动触发的时机
- auto-aof-rewrite-min-size : 文件大小
- auto-aof-rewrite-percentage : 当前 AOF 文件大小相比于上次重写 AOF 的文件大小所增加的比例
我们重点关注一下 AOF 重写的流程
3.4.2 重写流程
核心就是 fork 之前的数据直接写入到新的 AOF 文件中 , fork 之后收到的数据先由父进程暂存到 aof_rewrite_buf 缓冲区中 , 当子进程完成新的 AOF 文件写入之后再把 aof_rewrite_buf 缓冲区中的数据保存到新的 AOF 文件中
❓ 那如果我们执行 bgrewriteaof 命令 , 但是此时 Redis 已经正在进行 AOF 重写 , 会怎么办呢 ?
✔️ 此时 , 并不会再执行 AOF 重写 , 直接就返回了
❓ 那如果我们执行 bgrewriteaof 命令 , 但是此时 Redis 已经正在进行 RDB 重写 , 会怎么办呢 ?
✔️ 此时 , AOF 重写操作就会等待 , 等待 RDB 快照生成完毕之后 , 再进行执行 AOF 重写
❓ RDB 对于 fork 之后的新数据 , 是不去处理的 ; 但是 AOF 对于 fork 之后的新数据 , 就采用了 aof_rewrite_buf 缓冲区的方式来去处理的 . 那 RDB 为什么不参考 AOF 的实现思路来去实现 , 这样不就不会丢失数据了
✔️ RDB 的设计理念就是 “定期备份” , AOF 的设计理念才是 “实时备份”
❓ 父进程调用 fork 之后 , 就会让子进程去编写新的 AOF 文件 . 当子进程写完新的 AOF 文件的时候 , 就会通知父进程将 aof_rewrite_buf 缓冲区的数据也保存到新的 AOF 文件中 , 但是此时父进程依然在继续写马上就会被替换掉的旧的 AOF 文件 , 这有意义吗 ?
✔️ 有意义 , 我们要考虑一些极端情况 , 假设在 AOF 重写的过程中 , 重写了一半服务器挂了 . 那子进程中内存的数据就会丢失 , 新的 AOF 文件还不完整 , 所以如果父进程不继续保存数据到 AOF 文件 , 那重启之后就没有一个完整的 AOF 文件了 .
四 . 混合持久化
我们先来构造一组重复的数据
127.0.0.1:6379> set key 111
OK
127.0.0.1:6379> set key 222
OK
127.0.0.1:6379> set key 333
OK
# 上面做的都是无用功 , 最后 key 保存的就是 444
127.0.0.1:6379> set key 444
OK
然后我们此时来看一下 AOF 文件 , 使用命令 vim /var/lib/redis/appendonly.aof
然后翻到最底下
然后记录一下当前 appendonly.aof 文件的 inode 编号
此时 , 我们使用 bgrewriteaof 这个命令进行 AOF 重写
然后我们此时再来看一下 appendonly.aof 文件的内容 , 是否进行了简化 , 使用命令 vim /var/lib/redis/appendonly.aof
此时 , AOF 文件内容长度大大减少 , 同时数据也按照了二进制的方式进行了保存
但是有点似曾相识啊 , 这怎么跟 dump.rdb 长得这么相像
实际上 , AOF 原本是按照文本的方式来写入文件的 , 但是按照文本的方式写入文件 , 后续加载的成本是比较高的 . 所以 Redis 引入了混合持久化的方式 , 他结合了 AOF 和 RDB 的特点 .
首先 , 按照 AOF 的方式 , 将每一个请求都记录到 AOF 文件中 , 然后在触发 AOF 重写只会 , 就会把当前的内存状态按照 RDB 的二进制格式写入到新的 AOF 文件中 .
那我们之后再去添加新的数据 , 依然是按照 AOF 文本的方式追加到文件后面 .
当 Redis 上同时存在 AOF 文件和 RDB 快照的时候 , 以 AOF 文件为主 , 因为 AOF 中包含的数据比 RDB 中更全一些 .
五 . 对于信号的理解
在 bgsave 和 AOF 的重写中 , 都出现了一个步骤 : 信号通知父进程
那什么叫做信号呢 ?
信号可以认为是 Linux 系统中的神经系统 , 可以传递一些简单的信息 .
像我们上述的信息传递 , 只需要子进程简单的表达一下 “我干完了” , 就可以使用信号来去通知父线程进行一些操作
对于 Redis 的持久化机制我们已经讲解完毕 , 如果对你有帮助的话 , 还请一键三连~