当前位置: 首页 > news >正文

[Redis进阶]---------持久化

MySQL事务特性

MySQL的事务具有四大核心特性,这些特性对于保证数据库操作的准确性和可靠性至关重要。

  • ​原子性​​:事务中的所有操作要么全部成功,要么全部失败,不会出现部分操作成功的情况。
  • ​一致性​​:事务执行前后,数据库必须处于一致的状态,数据的完整性得到保证。[+比如,在电商系统中,用户下单后,库存数量应相应减少,订单状态应变为已支付,这些数据的变化必须保持一致。
  • ​持久性(持久化)​​:事务提交后,其结果是永久性的,即使系统崩溃也不会丢失。[+以电商平台的订单支付为例,一旦用户支付成功,该笔交易记录就会被永久保存,不会因为系统故障而丢失。]
  • 隔离性​​:多个事务并发执行时,相互之间不干扰,保证数据的独立性。[+例如,在电商系统中,多个用户同时下单购买同一件商品,系统应能正确处理这些并发事务,确保每个用户的订单都能正确生成,不会出现数据混乱的情况。

Redis持久化需求

MySQL的数据存储在硬盘中,天然支持持久化。而Redis的数据存储在内存中,这就需要特殊的持久化策略来实现数据的持久化存储。

​解决方法​​:当数据存储到内存时,复制一份同时存到硬盘中。当进程/主机重启时,从硬盘中读取数据写入内存中,这样就实现了数据的持久化存储。理论上来说,内存与硬盘中的数据是完全相等的,但有部分差异,这取决于不同的持久化策略。

代价​​:付出了额外的空间。不过硬盘资源比较划算,并不会增加很多的成本。

Redis持久化策略

简单分析一下RDB持久化和AOF持久化的不同

RDB:定期持久化,比如每个月把我电脑上的学习资料,整体进行备份到备份盘里(叫B) ,然后删除旧备份(叫A),再把新备份名字改为旧备份的名字(A->B),这实际就是RDB过程中的一部分

AOF:实时持久化,只要我下载了一个新的学习资料,就立即把这个学习资料在备份盘里备份一份

RDB(Redis DataBase)

理论:RDB持久化是把当前redis进程数据⽣成快照保存到硬盘的过程,触发RDB持久化过程分为⼿动触发和 ⾃动触发。

1. 手动触发RDB快照

  • ​​save命令​​:执行save命令时,Redis会全力以赴地进行快照操作,期间会阻塞其他Redis操作。这种方式的优点是简单直接,适用于对数据一致性要求极高,且可以接受短暂阻塞的场景。例如,在进行重要数据的备份时,可以使用save命令确保数据的完整性。
  • bgsave命令:Redis进程执⾏fork操作创建⼦进程,RDB持久化过程由⼦进程负责,完成后⾃动 结束。阻塞只发⽣在fork阶段,⼀般时间很短。(此处 Redis 使用的是 “多进程” 的方式来完成的并发编程,来完成 bgsave 的实现)
  • Redis 内部的所有涉及RDB的操作都采⽤类似bgsave的⽅式。

除了⼿动触发之外,Redis运⾏⾃动触发RDB持久化机制,这个触发机制才是在实战中有价值的。

  • 使⽤save配置。如"save m n"表⽰m秒内数据集发⽣了n次修改,⾃动RDB持久化。
  • 从节点进⾏全量复制操作时,主节点⾃动进⾏RDB持久化,随后将RDB⽂件内容发送给从结点。
  • 如果执行 shutdown 命令(service redis-server restart,正常关闭)关闭 Redis 时,或者通过正常流程重新启动 Redis 服务器,那么此时 Redis 服务器会在退出时自动执行 RDB 持久化。但如果是异常重启(kill -9 或者服务器掉电),那么此时 Redis 服务器来不及生成 rdb,内存中尚未保存到快照中的数据,就会随着重启而丢失。

如果插入新的 key,而此时不手动执行 bgsave,直接重新启动 Redis 服务器,那么刚刚插入的数据在重启之后仍然存在。所以说,Redis 生成快照操作不仅仅是手动执行命令才触发,也可以自动触发,也就是上面的第 3 点。

并不是说 Redis 客户端这边插入了数据,rdb 文件中的数据就会立即更新的。插入几个键值对后,没有运行手动触发的命令,也达不到自动触发的条件,那么就不会更新。

如果修改成如下内容:

对于 Redis 来说,配置文件发生修改后,一定要重新启动服务器才能生效。(当然,如果想要立即生效,也可以通过命令的方式进行修改)

同时满足两个条件即可触发快照的生成:

如果是修改成以下内容:

那么将会关闭自动生成快照这个功能。

对于手动触发和自动触发的解读:

RDB的bgsave执行流程

流程说明:bgsave 是主流的RDB持久化⽅式,下⾯根据图了解它的运作流程。

1.执⾏bgsave命令,Redis⽗进程判断当前进是否存在其他正在执⾏的⼦进程,如RDB/AOF⼦进 程,如果存在bgsave命令直接返回。
2.⽗进程执⾏fork创建⼦进程,fork过程中⽗进程会阻塞,通过infostats命令查看 latest_fork_usec 选项,可以获取最近⼀次fork操作的耗时,单位为微秒。
3.⽗进程fork完成后,bgsave命令返回"Backgroundsavingstarted"信息并不再阻塞⽗进程,可 以继续响应其他命令。
4.⼦进程创建RDB⽂件,根据⽗进程内存⽣成临时快照⽂件,完成后对原有⽂件进⾏原⼦替换。执⾏lastsave 命令可以获取最后⼀次⽣成RDB的时间,对应info统计rdb_last_save_time选 项。
5.进程发送信号给⽗进程表⽰完成,⽗进程更新统计信息。

bgsave 操作流程是创建子进程,子进程完成持久化操作(持久化速度太快了(数据少),难以观察到子进程),持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件(这个容易观察)。可以通过 Linux 的 stat 命令来查看文件的 inode 编号:

ps:fork()是Linux提供的系统调用,通俗来讲就是把父进程的PCB,地址空间,页表,文件描述符等复制一份给子进程,这样父进程中的内存数据子进程就得到了完全相同的一份,就可以进行持久化操作了

RDB文件的处理

  • 保存:RDB⽂件保存再dir配置指定的⽬录(默认/var/lib/redis/)下,⽂件名通过dbfilename 配置(默认dump.rdb)指定。可以通过执⾏configsetdir{newDir}和configsetdbfilename {newFilename} 运⾏期间动态执⾏,当下次运⾏时RDB⽂件会保存到新⽬录。
  • 压缩:Redis默认采⽤LZF算法对⽣成的RDB⽂件做压缩处理,压缩后的⽂件远远⼩于内存⼤ ⼩,默认开启,可以通过参数configsetrdbcompression{yes|no}动态修改。
  • 校验:如果Redis启动时加载到损坏的RDB⽂件会拒绝启动。这时可以使⽤Redis提供的redis check-dump⼯具检测RDB⽂件并获取对应的错误报告。

如何在Linux中进行搜索呢?

/+关键词,然后点击回车即可,n是下一个,N是上一个

如果把 rdb 文件故意改坏会怎么样?

手动把 rdb 文件内容改坏,如果是通过 service redis-server restart 重启,就会在 Redis 服务器退出时重新生成 rdb 快照,那么刚才改坏的文件就会被替换掉。

而如果是通过 kill 进程的方式,再重新启动 Redis 服务器,此时 rdb 文件就还是错的。但看起来 Redis 好像没有受到什么影响,还是能正常启动,能正确获取到 key。那是因为刚才修改的位置应该正好是文件的末尾,对前面的内容没有什么影响。但如果是修改了中间位置的内容,那么 Redis 服务器就启动不了了。 

此时,Redis 服务器挂了,可以看看 Redis 日志,了解一下发生了什么。

打开该文件,可以看到:

也就是在 rdb 恢复数据的过程中出现了问题。

rdb 文件是二进制的,如果直接把坏了的 rdb 文件交给 Redis 服务器去使用,那么得到的结果是不可预期的。所以,Redis 也提供了 rdb 文件的检查工具,可以先通过检查工具来检查一下 rdb 文件格式是否符合要求。

Redis 提供了专门用于检查 RDB 文件完整性的工具 redis-check-rdb,这是最直接可靠的方法。

基本用法:

redis-check-rdb /path/to/your/dump.rdb

RDB 的优缺点

(1)优点
  1. RDB 是⼀个紧凑压缩的二进制文件,代表 Redis 在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每 6 小时执行 bgsave 备份,并把 RDB 文件复制到远程机器或者文件系统中(如 hdfs)用于灾备。
  2. Redis 加载 RDB 恢复数据远远快于 AOF 的方式。 (二进制的方式则直接将数据读取到内存中,按照字节的格式取出来放到结构体 / 对象即可,文本方式组织数据则需要进行一系列的字符串切分操作)
(2)缺点
  1. RDB 方式数据没办法做到实时持久化 / 秒级持久化(在两次生成快照之间,实时数据可能会随着重启而丢失),这就导致快照里的数据和当前实时的数据情况可能存在偏差。因为 bgsave 每次运行都要执行 fork 创建子进程,属于重量级操作,频繁执行成本过高。
  2. RDB 文件使用特定二进制格式保存,Redis 版本演进过程中有多个 RDB 版本,兼容性可能有风险(旧版本的 Redis 的 rdb 文件放到新版本的 Redis 中不一定能实现。但一般来说,实际工作中 Redis 版本都是统一的,实在不行也可以通过写一个程序的方式来直接遍历旧的 Redis 中的所有 key,把数据取出来插入到新的 Redis 服务器中即可)。

AOF

AOF(Append Only File)持久化(类似于 MySQL 中的 binlog,会把用户的每个操作都记录到文件中):以独立日志的方式记录每次写命令,重启时再重新执行 AOF 文件中的命令达到恢复数据的目的。AOF 的主要作用是解决了数据持久化的实时性,目前已经是 Redis 持久化的主流方式

Redis 重新启动时,又读 RDB、又读 AOF,到底以哪个为准呢?

当开启 AOF 时,rdb 就不生效了,启动的时候就不再读取 rdb 文件内容。

1、使用 AOF

开启 AOF 功能需要设置配置:appendonly yes,默认是关闭状态。AOF 文件名通过 appendfilename 配置(默认是 appendonly.aof)设置。

保存目录同 RDB 持久化方式一致,通过 dir 配置指定(/var/lib/redis)。AOF 的工作流程操作:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load),如下图所示:

  1. 所有的写入命令会追加到 aof_buf(内存中的缓冲区,大大降低了写硬盘的次数)中。
  2. AOF 缓冲区根据对应的策略向硬盘做同步操作。
  3. 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
  4. 当 Redis 服务器启动时,可以加载 AOF 文件进行数据恢复。

2、命令写入

AOF 命令写入的内容直接是文本协议格式

每次进行的操作都会被记录到文本文件中,通过一些特殊符号作为分隔符,来对命令的细节做出区分。

此处遵守 Redis 格式协议,Redis 选择文本协议可能的原因:

  • 文本协议具备较好的兼容性。
  • 实现简单。
  • 具备可读性

AOF 过程中为什么需要 aof_buf 这个缓冲区?

Redis 虽然是使用单线程响应命令,但是速度很快,因为它只是操作内存。引入 AOF 之后,又要写内存,又要写硬盘,同时还要保持之前的速度。实际上,这并没有影响到 Redis 处理请求的速度。AOF 机制并非是直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区,积累一波之后再统一写入。如果每次写 AOF 文件都直接同步硬盘,性能从内存的读写变成 IO 读写,必然会下降。先写入缓冲区可以有效减少 IO 次数,同时,Redis 还可以提供多种缓冲区同步(刷新)策略,让用户根据自己的实际情况和需求来做出合理的平衡。

如果把数据写入到缓冲区中,其本质还是在内存中。万一此时突然进程挂了或者主机掉点了,那缓冲区中的数据是否就丢了呢?

是的。缓冲区中没来得及写入硬盘的数据是会丢失的。

  • 缓冲区刷新频率越高,性能影响就越大,同时数据的可靠性就越高。
  • 缓冲区刷新频率越低,性能影响就越小,同时数据的可靠性就越低。 

3、文件同步

Redis 提供了多种 AOF 缓冲区同步文件策略,由参数 appendfsync 控制,不同值的含义如下表所示。

AOF 缓冲区同步文件策略:

默认采用 everysec 配置:

  • 配置为 always 时,每次写入都要同步 AOF 文件,性能很差,在⼀般的 SATA 硬盘上,只能⽀持大约几百 TPS 写入。除非是非常重要的数据,否则不建议配置。
  • 配置为 no 时,由于操作系统同步策略不可控,虽然提高了性能,但数据丢失风险大增,除非数据重要程度很低,一般不建议配置。
  • 配置为 everysec,是默认配置,也是推荐配置,兼顾了数据安全性和性能。理论上最多丢失 1s 的数据。

4、重写机制

随着命令不断写入 AOF,文件持续增长,体积会越来越大,会影响到 Redis 下次启动的启动时间(Redis 在启动时需要读取 AOF 文件内容,该文件记录了中间的过程,但实际上 Redis 在重启时只关注最终结果)为了解决这个问题,Redis 引入 AOF 重写机制(能够针对 AOF 文件进行整理操作,剔除其中的冗余操作并合并一些操作,以此达到给文件 “瘦身” 的效果)压缩文件体积。AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新的 AOF 文件。

重写后的 AOF 为什么可以变小?

  • 进程内已超时的数据不再写入文件。
  • 旧的 AOF 中的无效命令,例如 del、hdel、srem 等重写后将会删除,只需要保留数据的最终版本。
  • 多条写操作合并为⼀条,例如 lpush list a、lpush list b、lpush list 从可以合并为 lpush list a b c。

较小的 AOF 文件一方面降低了硬盘空间占用,一方面可以提升启动 Redis 时数据恢复的速度。

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

  1. 手动触发:调用 bgrewriteaof 命令。
  2. 自动触发:根据 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 参数确定自动触发时机。
  • auto-aof-rewrite-min-size:表示触发重写时 AOF 的最小文件大小,默认为 64MB。
  • auto-aof-rewrite-percentage:代表当前 AOF 占用大小相比较上次重写时增加的比例。

当触发 AOF 重写时,下图介绍它的运行流程。

  1. 执行 AOF 重写请求。如果当前进程正在执行 AOF 重写,请求不执行。如果当前进程正在执行 bgsave 操作,重写命令延迟到 bgsave 完成之后再执行。
  2. 父进程执行 fork 创建子进程。
  3. 重写。a. 父进程 fork 之后,继续响应其他命令,仍然负责接收客户端的新的请求。父进程还是会把这些请求产生的 AOF 数据先写入到缓冲区中并根据 appendfsync 策略同步到硬盘,保证旧 AOF 文件机制正确。b. 子进程只有 fork 之前的所有内存信息,父进程中需要将 fork 之后这段时间的修改操作写入 AOF 重写缓冲区中。重写的时候不关心 AOF 文件中原来有什么,只关心内存中最终的数据状态。(内存中的数据的状态,就已经相当于是把 AOF 文件结果整理后的模样了)
  4. 在创建子进程的一瞬间,子进程就根据内存快照,将命令合并到新的 AOF 文件中,也就是继承了当前父进程的内存状态。因此,子进程里的内存数据是父进程 fork 之前的状态。fork 后新来的请求对内存造成的修改是子进程感知不到的。此时,父进程这里又准备了一个 aof_rewrite_buf 缓冲区(专门存放 fork 后收到的数据)。
  5. 子进程完成重写:
  • 子进程这边把 AOF 新文件数据写入完成后,子进程会通过发送信号通知父进程。

  • 父进程再把 aof_rewrite_buf 缓冲区中的内容也追加到新 AOF 文件里。

  • 用新 AOF 文件代替旧 AOF 文件。

此处子进程写数据的过程非常类似于 RDB 生成一个镜像快照,只不过 RDB 是按照二进制的方式来生成的,AOF 重写则是按照 AOF 这里要求的文本格式来生成的。二者目的都是为了把当前内存中的所有数据状态记录到文件中

如果在执行 bgrewriteaof 的时候,发现当前 Redis 已经正在进行 AOF 重写了,会怎样呢?

此时不会再次执行 AOF 重写,而是直接返回了。

如果在执行 bgrewriteaof 的时候,发现当前 Redis 在生成 rdb 文件的快照,会怎样呢?

此时 AOF 重写操作就会等待 rdb 快照生成完毕之后再进行执行 AOF 重写。

为什么 RDB 对于 fork 之后的新数据就直接置之不理了呢,而不选择采用和 AOF 一样的处理机制呢? 

RDB 本身的设计理念就是用来 “定期备份” 的。只要是定期备份就难以和最新数据保持一致。

实时备份不一定就比定期备份更好,具体还是要看实际场景需求。现在的系统中,系统资源一般都是比较充裕的,AOF 的开销并不大,所以一般来说,AOF 的适用场景更多一些。

父进程 fork 完毕之后,就已经让子进程写新的 AOF 文件了。并且随着时间的推移,子进程很快就写完了新的文件,要让新的 AOF 文件代替旧的 AOF 文件。父进程此时还在继续写这个即将消亡的旧的 AOF 文件是否还有意义呢?

需要考虑到极端情况:假设在重写的过程中,重写了一半服务器突然挂了,此时子进程内存的数据就会丢失,新的 AOF 文件内容还不完整,所以如果父进程不坚持写旧的 AOF 文件,那么重启就无法保证数据的完整性了。

AOF 本来是按照文本的方式来写入文件的,但是文本的方式来写文件,后续加载的成本较高。所以,Redis 就引入了 “混合持久化” 的方式,结合了 rdb 和 aof 的特点。按照 aof 的方式,将每一个请求 / 操作都记录入文件中。在触发 aof 重写之后,就会把当前内存的状态按照 rdb 的二进制格式写入到新的 aof 文件中。

后续再进行的操作仍然是按照 aof 文本的方式追加到文件后面:

5、启动时数据恢复

当 Redis 启动时,会根据 RDB 和 AOF 文件的内容,进行数据恢复,如下图所示。

RDB 和 AOF 的区别和联系

  • RDB 和 AOF 是 Redis 提供了两种持久化方案。
  • RDB 视为内存的快照,产生的内容更为紧凑,占用空间较小,恢复时速度更快。但产⽣ RDB 的开销较大,不适合进行实时持久化,一般用于冷备和主从复制。
  • AOF 视为对修改命令保存,在恢复时需要重放命令,并且有重写机制来定期压缩 AOF 文件。
  • RDB 和 AOF 都使用 fork 创建子进程,利用 Linux 子进程拥有父进程内存快照的特点进行持久化,尽可能不影响主进程继续处理后续命令。

http://www.dtcms.com/a/347903.html

相关文章:

  • std::uncaught_exceptions 详解
  • 大模型——深度评测智能体平台Coze Studio
  • 【Cmake】cmake_minimum_required,project,include,install,add_executable
  • 关于链式二叉树的几道OJ题目
  • 【Java SE】抽象类与Object类
  • 什么是正态分布
  • Mysql InnoDB 底层架构设计、功能、原理、源码系列合集【五、InnoDB 高阶机制与实战调优】
  • Manus AI 与多语言手写识别技术文章大纲
  • 夜间跌倒漏报率↓78%!陌讯多模态算法在智慧养老院的精准监测方案
  • Python 地理空间分析:核心库与学习路线图
  • 【三维重建】第二章 Python及Pytorch基础
  • 关于说明锂电池充电芯片实际应用
  • Python Excel
  • C++项目实战——高性能内存池(四)
  • Nacos-11--Nacos热更新的原理
  • 循环中的阻塞风险与异步线程解法
  • 综合测验:配置主dns,dhcp,虚拟主机,nfs文件共享等
  • 操作系统知识
  • (一)算法(big O/)
  • claude-code+kimi实测
  • 当AI成了“历史笔迹翻译官”:Manus AI如何破解多语言手写文献的“密码锁”
  • Redis优缺点
  • leetcode80:删除有序数组中的重复项 II(快慢指针法)
  • 历史数据分析——半导体
  • 5.在云服务器上部署RocketMQ以及注意点
  • 双指针:三数之和
  • SQL注入1----(sql注入原理)
  • 深入理解 OPRF 技术:盲化伪随机函数的原理、对比与应用
  • UE 官方文档学习 C++TArray 移除操作
  • C++11: std::weak_ptr