目录
????????????????Redis 实现持久化的两大策略
????????????????????????bgsave 命令执行流程
- 什么是持久化?
回答:
- 将数据存储在硬盘上——> 持久
- 将数据存储在内存上 ——> 不持久
- 所以持久化的判断标准为 重启进程 或 重启主机 后,数据是否存在!
- Redis 是一个内存数据库,即将数据存储在内存中
- 但内存中的数据是不持久的,要想能够做到持久,就需要让 Redis 将数据存储到硬盘上
- Redis 相比于 MySQL?关系型数据库,其最明显的特优势为 效率高、速度快
- 所以为了保证速度快,Redis 的数据肯定还是得存储在内存中
- 但是为了持久化,Redis 的数据还得想办法存储在硬盘上
- 随之 Redis 决定在 内存 中存储数据的同时 硬盘 上也进行数据的存储
通俗理解:
- 当插入一个新数据时,将该数据同时写入到内存和硬盘
- 当查询某个数据时,直接从内存读取
- 硬盘上的数据只是在 Redis 重启的时候,用来恢复内存中的数据的
代价:
- 消耗了更多的空间,即同一份数据存储了两遍
- 但是由于硬盘比较便宜,因此这样的开销并不会带来多大的成本
注意点一:
- 内存上 和 硬盘上 的两份数据,理论上是完全相同的
- 但实际上可能存在一些小差异,这完全取决于我们如何针对数据进行持久化
注意点二:
- 说是两边都写,但实际上具体怎么写硬盘还有着不同的策略
- 但是可以保证的是 整体的效率还是足够高的
Redis 实现持久化的两大策略
- RDB ——> Redis DataBase(定期备份)
- AOF ——> Append Only File(实时备份)
注意:
- Redis 服务器配置文件默认开启?RDB(定期备份)
- RDB 定期将?Redis 内存中的所有数据都给写入到硬盘中,即生产一个 "快照"
通俗理解:
- Redis 给当前在内存中存储的所有数据拍个照片,生成一个 rdb 文件,并将其存储在硬盘中
- 如果后续 Redis 服务器一旦重启了(内存数据就没了),就可以根据刚才的 "快照" 将内存中的数据给恢复回来
- RDB 定期备份具有两种触发方式,分别为 手动触发、自动触发
- 程序员通过 Redis 客户端执行特定的命令,以此触发 "快照" 生成
save 命令
- 执行 save 命令时,Redis 会全力以赴的生成?"快照"?,此时就有可能阻塞 Redis 的其他客户端命令
注意:
- 使用改命令时,可能会导致类似于 keys * 的后果
- 所以一般不建议使用 save 命令
bgsave 命令
- bg ——> backgroud(后面)即不会影响 Reids 服务器处理其他客户端的请求和命令
问题:
- Redis 时如何做到的呢? 是否弄了个多线程来执行该命令??
回答:
- 此处 Redis 使用?多进程?的方式来完成的并发编程,从而完成的 bgsave 命令的实现
bgsave 命令执行流程
第一步
判定当前是否已经存在其他正在工作的子进程
比如现在已经有一个子进程正在执行 bgsave,此时将直接把当前的 bgsave 返回
第二步
- 如果没有其他的工作子进程,就通过 fork 这样的系统调用来创建一个子进程
重点理解:
- Java 进行并发编程时,主要通过 多线程 的方式
- fork 是 linux 系统提供的一个创建子进程的 api (系统调用)
- fork 会直接将当前的进程(父进程)复制一份,作为子进程
- 复制完成之后,父子进程就变为两个独立进程,即各自执行各自的
- 此处所说的复制操作 会复制 pcb?虚拟地址空间(内存中的数据)、文件描述符表等
- 本来 Redis 服务器中有若干变量,即保存了一些键值对数据
- 随着?fork 的进行,子进程的这个内存里也会存在和刚才父进程中一模一样的变量(键值对数据)
- 因此子进程 内存中的数据 和 父进程 内存中的数据 相同
- 接下来便安排子进程去执行 持久化 操作,也就相当于把父进程本体这里的内存数据给持久化了
注意:
- 正因为 父进程文件描述符表 也被 子进程 给复制了
- 所以当父进程打开了一个文件 且??fork 了之后,子进程也是可以同样使用这个文件的
- 因此也就导致了子进程持久化写入的那个文件和父进程本来要写的文件是同一个
问题:
- 如果当前 Redis 服务器中存储的数据特别多,内存消耗特别大(比如 100G)
- 此时进行上述的复制操作是否会有很大的内存开销?
回答:
- 此处的性能开销其实挺小的
- fork 在进行内存拷贝时,不是无脑的直接将所有数据均拷贝一遍,而是以 写时拷贝 的机制来完成的
- 如果子进程里的这个内存数据和父进程的内存数据完全一致,此时就不会触发真正的拷贝动作,而是子父进程共用同一份内存数据
- 但是子父进程的内存空间各自独立
- 一旦某个内存数据做了修改,此时便会立即触发真正的 物理内存 上的数据拷贝
小总结:
- 在进行执行 bgsave 命令时,绝大部分的内存数据是不需要改变的
- 整体来说这个过程执行还挺快的
- 因为短时间内,父进程中不会有大批的内存数据的变化
- 因此子进程?写时拷贝 并不会触发很多次,也就保证了整体的拷贝时间是可控的、高效的
第三步
- 子进程负责进行 写文件,生成快照的过程
- 父进程继续接收客户端的请求,继续正常提供服务
第四步
- 子进程完成整体的持久化过程之后,就会通知父进程,干完了,父进程就会更新一些统计信息
- 执行至此?子进程 便可以结束并销毁了!
自动触发 Redis 生成快照的操作有多种方式:
- 通过刚才配置文件中? save 执行 M 时间内,修改 N 次
- 通过 shutdown 命令(redis 里的一个命令)关闭 redis 服务器,也会触发(正常关闭)
- 在 Redis 进行主从复制时,主节点也会自动生成 rdb 快照,然后将?rdb 快照文件内容传输给从节点
注意:
- 但在实际开发中,更害怕的是出现异常情况!
- Rdis 的配置文件中关于方式一如下图所示
方式一的触发条件:(默认值)
- 900 秒内 至少 1 次 key 的修改
- 300 秒内 至少 10 次 key 的修改
- 60 秒内 至少 10000 次 key 的修改
注意:
- 虽然此处的数值可以自由修改配置
- 但是,此处修改上述数据的时候,要有一个基本的原则
原则:
- 生成一次?rdb "快照",是高成本的操作,所以不能让该操作执行的太频繁!
- 在默认配置中,生成两次 rdb "快照" 之间的间隔最少也为?60秒
小总结:
- 正因为 rdb "快照"生成的不能太频繁
- 因此也就导致 "快照" 里的数据和当前实时的数据情况可能存在一定偏差
实例理解
- Redis 生成的 rdb 文件存放在 Redis 的工作目录中(?Redis 配置文件可自行设置)
- RDB 机制生成的镜像文件为二进制的文件
- 即将内存中的数据以压缩的形式保存到这个二进制文件中
- 而 压缩 需要消耗一定的 CPU 资源,但是能节省空间
注意点一:
- Redis 服务器重新启动,就会尝试加载这个 rdb 文件
- 如果发现格式错误,就可能会加载数据失败!
- rdb 文件,在我们不主动修改它的前提下,也可能会存在一些意外情况
- 即 一旦通过一些操作(比如网络传输)引起 rdb 文件被破坏,此时 Redis 服务器就无法启动
ps:
- redis 提供了 rdb 文件的检查工具 ——> redis-check-rdb*
注意点二:
- rdb 持久化操作可触发多次
- 当执行生成 rdb 镜像操作的时候
- 此时就会把要生成的快照数据,先保存到一个临时文件中
- 当这个快照生成完毕之后,再删除之前的 rdb 文件,把新生成的 rdb 文件名字改成刚才的 dump.rdb
- 因为从始至终,rdb 文件有且仅有一个
- bgsave 操作流程是创建子进程,子进程完成持久化操作
- 持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件
注意:
- 因为 持久化速度太快了(数据少),我们难以观察到 子进程
- 但是 使用新文件替换旧文件 这个是可以观察到的
实例理解
- 使用 linux 的 stat 命令,查看文件的 inode 编号
补充点一:Linux 文件系统
- 文件系统典型的组织方式(ext4)主要把整个文件系统分成了三个大的部分:
- 超级块(放的是一些管理信息)
- inode 区(存放 inode 节点 每个文件都会分配一个 inode 数据结构,包含了文件的各种元数据)
- block 区(存放文件的数据内容)
补充点二:
- 直接使用 save 命令不会触发 子进程 和?文件替换 逻辑
- 则直接在当前进程中,往刚才的同一个文件中写入数据
- 根据该实例演示,我们来仔细观察新 rdb 文件的生成
1、Redis 未设置 任何键值对的 dump.rdb 文件
2、向 Redis 设置?3个键值对
3、再次打开?dump.rdb 文件,此时并未发生任何变化
注意:
- rdb 文件中的数据,不是你这边插入了数据,就会立即更新的
RDB 机制的触发时机:
- 手动触发(save、bgsave)
- 自动触发
原因:
- 正因为刚才插入 3个键值对,没有运行手动触发的命令,也达不到自动触发的条件
- 因此?dump.rdb 文件并未发生改变?
4、此时我们手动执行 bgsave 命令触发一次生成快照
- 由于此处的数据比较少,执行 bgsave 命令后 一瞬间便完成了
5、立即查看应该就是有结果的
- 此处我们可以看到 dump.rdb 中已经发生了改变!
注意:
- 如果 Redis 中的数据多,执行 bgsave 命令就可能需要消耗一定的时间
- 立即查看不一定就是生成完毕了
6、将现在正在运行的 Redis 服务器重启,看是否能恢复内存之前的状态
- 由上图可知,Redis 服务器在重新启动时,加载了 rdb 文件的内容,恢复了内存之前的状态
7、向 Redis 中设置新的键值对,但不手动执行 bgsave 命令直接重启 Redis 服务器
- 刚才没有执行 bgsave 命令,但是这个 key4 居然在重启之后仍然存在!
- 自动触发 ——> 方式二
8、打开并查看 dump.db 文件
- 当 Redis 服务器出现异常情况时会导致什么后果?
- 即 非正常关闭 Redis 服务器
1、此处我们使用 kill -9 命令来模拟异常情况的出现
- 由上图可知,在 Redis 重启之后,key5 丢失!
2、打开并查看 dump.db 文件
总结:
- 如果是通过正常流程重新启动 Redis 服务器(kill 命令),此时 Redis 服务器会在退出时,自动触发生成 rdb 操作
- 但是 如果是异常重启(kill -9 命令 或者 服务器掉电),此时 Redis 服务器便来不及生成 rdb,即内存中尚未保存到快照中的数据,就会随着重启而丢失
- 此处我们演示观察 自动触发(通过配置)生成 rdb 快照
- 即在 Redis 配置文件中,设置让 Redis 每隔多长时间并产生多少次修改 就触发
1、执行 FLUSHALL?命令,清除前面测试的键值对(执行 FLUSHALL?也会清空 rdb 文件)
2、修改配置文件
3、重启服务器,并插入 2个键值对
注意:
- redis 来说,修改配置文件之后,一定要重启服务器,才能生效!
4、等待 60秒,查看 dump.rdb 文件
- 由上图可以,redis 成功生成 rdb "快照"
- 如果将?rdb 文件故意改坏会导致什么后果?
1、手动的将?rdb 文件内容改坏
2、通过 kill -9 命令的方式重新启动 Redis 服务器
- 由上图可知,Redis 服务器启动失败!
注意:
- 如果仅通过 kill 命令的方式重启,就会在 Redis 服务器退出时,重新生成 rdb 快照
- 此时新生成的 rdb 文件便会将刚改坏了的旧文件给替换掉
- 因此无法观察到效果!
3、查看 Redis 的日志
具体解释:
- rdb 文件为二进制文件
- 上述操作所导致的后果为 直接改坏的 rdb 文件交给 Redis 去使用?
- 此处得到的结果是不可预期的
- 可能 Redis 服务器能启动,但是得到的数据可能正确也可能也有问题
- 也有可能 Redis 服务器直接启动失败(如上述情况所示)
补充:
- 但 Redis 提供了 rdb 文件的检查工具 ——>?redis-check-rdb
- 所以我们可以在启动 Redis 服务器之前,先通过 检查工具,检查?rdb 文件格式是否符合要求
- 运行的时候,加入 rdb 文件作为命令行参数 ——> redis-check-rdb dump.rdb
- 此时就是以检查工具的方式来运行,不会真的启动 redis 服务器
- RDB 是一个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照,非常适用于备份、全量复制等场景,比如每 6小时执行 bgsave 备份,并把 RDB 文件复制到远程机器或者文件系统中
- Redis 加载 RDB 恢复数据远远快于 AOF 方式
- RDB 方式数据没办法做到实时持久化 或 秒级持久化,因为 bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高
- RDB 文件使用特定二进制格式保存,Redis 版本演变过程中有多个 RDB 版本,兼容性可能有风险
注意:
- RDB 使用二进制的方式来组织数据,直接把主句读取到内存中,按照字节的格式取出来,并放到 结构体 / 对象 中即可
- AOF 是通过文本的方式来组织数据的,则需要进行一系列的字符串切分操作
补充:
- 一个老版本?Redis 的 rdb 文件,放到新版本?Reids 中不一定能识别的了
- 在一般的实际工作当中,Redis 版本都是统一的
- 如果确实有一些升级版本的需求,即遇到了不兼容的问题
- 就可以通过写一个程序的方式,直接遍历旧 Redis 服务器中的所有 key,把数据取出来,插入到新?Reids 服务器中即可
总结:
- RDB 最大的问题,不能实时的持久化保存数据
- 在两次生成快照之间,实时的数据可能会随着重启而丢失