redis持久化

文章目录

Redis持久化的设计方针

Redis的数据不是存储在Redis服务器内存中吗,那内存中的数据一断电就消失了,如何实现持久化呢?
Redis是一个 内存 数据库,即数据存储在内存中!!但是内存中的数据是不持久的~~ 要想能够做到持久,就需要让redis把数据存储到硬盘上

为了保证速度快,数据肯定还是得在内存中.但是为了持久,数据还得想办法存储在硬盘上……面对这样的冲突,Redis是如何解决的呢?

Redis决定内存中存一份数据, 硬盘上也存一份数据。
这两份数据,理论上是完全相同的,实际上可能存在一个小的概率有差异~~取决于咱们具体怎么进行持久化~

当要插入一个新的数据的时候,就需要把这个数据,同时写入到内存和硬盘,说是两边都写,但是实际上具体怎么写硬盘还有不同的策略

当查询某个数据的时候,直接从内存读取。硬盘的数据只是用来在 redis 重启的时候恢复内存中的数据的
代价就是消耗了更多的空间. 同一份数据,存储了两遍(但是毕竟硬盘比较便宜,这样的开销并不会带来太多的成本 )

Redis持久化的实现策略

Redis持久化的实现策略主要是两种,一种是RDB,另一种是AOF

  1. RDB => Redis DataBase
  2. AOF => Append Only File

RDB和AOF两种持久化实现策略的区别是什么?

RDB和AOF都是用来对硬盘进行备份的策略,所谓的硬盘备份,就是将一张硬盘中的数据备份到另一张硬盘中

  • RDB:定期备份
    每个月的1号,都会定期把我电脑硬盘上的学习资料,整体的备份到这个备份盘中~~

  • AOF:实时备份
    只要我下载了一个新的学习资料,立即把这个学习资料往备份盘中拷贝一份~~

硬盘本身不就是用来对内存进行备份的吗,为什么还要对硬盘进行备份呢?

一台电脑上,硬盘是最容易坏的部分!

  • CPU?如果你把 CPU 用坏了,恭喜你,可以去买彩票~
  • 显卡?除非你是高强度挖矿~~(矿卡)
  • 内存?概率非常低~~
  • 硬盘反而是最容易出问题的,尤其是 “机械硬盘”

我自己的电脑上有很多的 “学习资料”。虽然现在是把学习资料 保存在硬盘上(是持久化保存),但是万一有一天我的电脑出现故障了(硬盘坏了),我几十年来收集的学习资料不就毁于一旦了嘛。为了防止这种情况出现,我就拿另外一块移动硬盘,来作为备份用的硬盘,这就是为什么要进行硬盘备份

RDB策略

RDB策略:RDB策略 会定期把我们 Redis 内存中的所有数据(所有键值对),都写入硬盘中,生成一个“快照”(dump.rdb)

举个例子:某个案发现场,警察来了
=> 拉上警戒线,然后警察们就开始忙碌的拍照
=> 记录现场
=> 后续就可以根据这些记录的照片,来还原出现场当时发生了什么

当到了RDB策略规定的时刻,Redis 就会给内存中当前存储的这些数据赶紧拍个照片,生成一个rdb文件,存储在硬盘中,后续 Redis 一旦重启(内存数据没了),我们就可以根据刚才的“快照”,把内存中的数据给恢复回来

dump.rdb文件介绍

redis生成的rdb文件,是存放在redis的工作目录中的。具体存放在哪里(默认在/var/lib/redis/目录下),具体叫什么名字(默认叫dump.rdb),都可以在redis配置文件redis.conf中进行设置
在这里插入图片描述

打开redis.conf中设置的指定路径,我们发现了一个dump.rdb文件
在这里插入图片描述

这个文件就是redis根据RDB策略定期生成的redis快照文件。redis定期会将当前时刻redis服务器中的数据形成一个快照,备份到磁盘中,这个磁盘备份文件就是dump.rdb。从这里我们也可以看出,redis服务器默认就会开启RDB策略

这个dump.rdb,其实是一个二进制类型的文件。redis在备份的时候,会把内存中的数据压缩之后,保存到dump.rdb这个二进制文件中。为什么要先压缩再保存?为了节省空间呗。

对于这个dump.rdb,我们最多拿 vim 打开看看就得了,不要乱改,一旦要是把数据的格式改坏了,就麻烦了。因为后续 redis 服务器重新启动,就会尝试加载这个 rdb 文件. 如果发现格式错误,就可能会加载数据失败

这个dump.rdb 文件,即使咱们不去主动vim修改它,还是有可能会出现一些意外,比如网络传输过程中,数据传输出错。一旦通过一些意外导致这个文件被破坏,此时 redis 服务器就无法启动

为什么dump.rdb 文件被破坏,redis就无法正常启动?

这个dump.rdb中记录的是redis数据库中所有的键值对数据,你redis服务器启动的时候,肯定是要根据dump.rdb,将其解压之后的redis数据库数据都读入到内存中。如果这个文件被破坏,那数据库就没法正常恢复,redis服务器肯定无法正常启动。
为了在启动之前检测dump.rdb 文件是否被破坏,redis 提供了 rdb 文件的检查工具(redis-check-rdb*),用于检查这个文件格式是否正确,有没有被破坏。

我们都知道,RDB策略要求定期生成dump.rdb文件,那我生成新的dump.rdb文件之后,老dump文件如何处理?/var/lib/redis/目录下,会不会存在多个时间版本的dump.rdb文件?

当redis更新 rdb 文件的时候,会把要生成的快照数据,先保存到一个临时文件中,当这个快照生成完毕之后,再删除之前老的 rdb 文件,把新生成的临时的 rdb 文件名字改成刚才的 dump.rdb。这样的做法就能保证,自始至终rdb 文件都只有一个的

我在redis客户端输入了几条插入命令,请问redis服务器执行完毕之后,会不会立即更新dump.rdb文件?

大概率不会,因为此时没有save或者bgsave指令手动触发dump.rdb文件的更新,如果没到定期更新的时间点,也不会触发自动更新

rdb文件的更新

dump.rdb文件什么时候更新?如何更新呢?
rdb文件更新的触发方式有手动触发和自动触发两种

1. 手动触发.

手动触发指的是程序猿通过 redis 客户端,执行特定的命令,来触发快照生成.

save命令

程序员在命令行中输入 save 命令之后,redis 就会全力以赴的进行“快照生成”操作,而由于redis服务器是单线程,一根筋,干这个的时候,就没办法响应其他的指令,导致类似于 keys * 的后果——阻塞redis主线程,导致 redis 其他客户端的命令被阻塞无法执行。所以一般不建议使用 save

bgsave命令
  • bg => background (后台),也就是说bgsave是在后台进行备份,不会影响 Redis 服务器处理其他客户端的请求和命令
Redis服务器不是单线程模型吗?那它是如何做到一边处理客户端请求,一边在后台进行备份的呢?是不是偷摸搞了个多线程啥的?

并非如此!!

  • 当执行 BGSAVE 命令时,Redis 主线程会调用操作系统提供的 fork 函数。fork 函数会创建一个与主线程完全一样的子进程。后续的持久化工作就由这个子进程来完成。
  • 子进程创建成功后,由子进程负责将内存数据写入磁盘生成 RDB 文件。
  • 在这个过程中,主线程仍然可以继续处理客户端的请求。因为主线程只是读取内存数据(执行读命令,或者执行写命令但还未修改到共享内存页),所以不会与子进程产生冲突。
  • 只有当主线程需要修改共享内存页中的数据时,操作系统才会为其分配新的物理内存页,并将原有数据复制过去,从而保证子进程的数据不受影响,能够正常完成 RDB 文件的生成。
    在这里插入图片描述

一个客户端执行bgsave命令的流程

  1. 首先要判定当前是否已经存在其他正在工作的子进程.
    比如现在已经有一个子进程正在执行 bgsave,此时就直接把当前的 bgsave 返回

  2. 如果没有其他的工作子进程,就通过 fork 这样的系统调用创建出一个子进程来

  3. 子进程负责进行 写文件,生成快照的过程.父进程继续接收客户端的请求,继续正常提供服务.

    • 父进程调用fork 创建子进程,一旦复制完成了,父子进程就是两个独立的进程,就各自执行各自的了
    • 然后由子进程去执行持久化的任务,父进程(Redis主线程)继续监听并响应客户端发来的其他命令
  4. 子进程完成整体的持久化过程之后,就会通知父进程,干完了,父进程就会更新一些统计信息. 子进程就可以结束销毁了.

    • 当子进程成功完成持久化,生成了dump.rgb文件,他就会给父进程发送一个SIGUSR1信号,告知父进程自己滴任务完成辣(当然失败了也会给父进程发信号,告诉父进程任务失败了),然后父进程根据任务的完成情况进行后续处理
我不是要把父进程的数据持久化到硬盘上吗?这个工作能交给子进程来干吗?

子进程与父进程共享进程地址空间,父进程中的许多数据,子进程都能看得到,因此父进程数据的持久化工作,当然可以由子进程来做

为什么这里主线程是创建一个子进程用于持久化,而不是创建一个子线程呢?
  1. 多核情况下,父子进程也能分别上两个CPU,实现真正的并行,多线程带来的速率提高并不明显
  2. 父子进程确保数据一致性的方案更加简单高效
    • 多线程共享进程地址空间中的数据,为了保证数据一致性(比如避免读取到一半被修改的“脏数据”),必须引入复杂的同步机制(如互斥锁、读写锁)。而锁的引入会导致主线程在执行命令时频繁等待(如子线程持有读锁时,主线程写操作需要阻塞),严重影响 Redis 作为“高性能内存数据库”的核心优势。
    • 而如果创建一个子进程,由于进程的隔离性更好,父子进程对同一数据的操作有写时拷贝机制进行隔离,不用复杂的同步机制,也能确保数据的一致性。当主线程需要修改某块内存时,操作系统会为这块内存创建一个副本(写时复制),子进程仍访问旧版本数据,双方互不干扰。
  3. 父子进程之间的“隔离性”更高,子进程挂了对父进程的影响很小
    • 如果使用父子进程,进程间的隔离性更高,可以保证:

      • 子进程的崩溃(如磁盘满导致写入失败)不会影响父进程(主线程)的正常运行。
      • 子进程的 IO 操作(写入大文件)不会阻塞主线程(IO 操作在子进程中独立执行,不占用主线程的 CPU 时间)。
    • 而如果用子线程,线程崩溃可能直接导致整个进程退出,且线程共享文件描述符等资源,更容易相互干扰。

上述三点总结来说:父子进程之间的“隔离性”更高,更适配数据库事务的隔离性要求。Redis 选择子进程而非子线程处理持久化,本质是用操作系统的 fork 机制和写时复制技术,替代复杂的多线程同步,在保证数据一致性的同时,最大限度减少对主线程性能的影响。这一设计与 Redis“单线程核心 + 利用操作系统特性”的整体架构高度契合,是权衡性能、复杂度和稳定性后的最优选择。

如果当前Redis 服务器中存储的数据特别多,内存消耗特别大(比如 100GB),此时,fork过程中的复制操作,是否会有很大的性能开销?

此处的性能开销,其实挺小的,这是因为fork在进行内存拷贝的时候,不是简单无脑的直接把所有数据都拷贝一遍,而是通过“写时拷贝”的机制来完成的!!
如果子进程里的这个内存数据,和父进程的内存数据完全一样了,此时就不会触发真正的 拷贝 动作. (而是爷俩其实共用一份内存数据),一旦某一方针对这个内存数据做了修改,才会触发真正的 物理内存 上的数据拷贝

在进行 bgsave 这个场景中,绝大部分的内存数据,是不需要改变的(整体来说这个过程执行的还挺快的,这个短时间内,父进程中不会有大批的内存数据的变化)
也就是说,子进程的“写时拷贝”并不会触发很多次,也就保证了整体的“拷贝时间”是可控的,高效的

如何手动触发?

打开redis客户端,命令行直接输入指令bgsave即可
在这里插入图片描述

由于咱们这里的数据比较少,执行bgsave瞬间就完成了。立即查看应该就是有结果的。如果以后咱们接触到的数据多了,执行bgsave就可能需要消耗一定的时间。立即查看不一定就是生成完毕了~~

redis服务器在重新启动的时候,会自动加载rdb文件的内容,此时我们前面添加的数据就已经持久化保存到redis数据库中了

验证bgsave的操作流程

根据bgsave的原理可知,bgsave的操作流程是创建子进程,子进程完成持久化操作。 持久化会把数据写入到新的文件中,然后使用新的文件替换旧的文件。那我们要验证这一操作流程,无非就是观察bgsave执行过程中是否创建出了子进程,以及是否用新的文件替换旧的文件

如果redis数据库中的数据比较少,那持久化速度就会非常快,子进程很快完成了任务,然后就销毁了,因此这时候如果我们速度不够快,就难以观察到子进程

如何观测新的文件替换旧的文件?
至于用新的文件替换旧的文件,这个是容易观察到的,因为我们可以使用linux的stat命令,查看文件的inode编号, 如果前后两个dump.rdb的inode编号不同,就说明这两个dump.rdb不是同一个文件

补充知识:linux文件系统
Linux文件系统典型的组织方式(ext4)主要是把整个文件系统分成了三个大的部分~~

  1. 超级块(放的是一些管理信息)
  2. inode区(存放inode节点。每个文件都会分配一个inode数据结构,包含了文件的各种元数据)
  3. block区,存放文件的数据内容
对比: save与bgsave
  • 通过bgsave进行rdb快照:主进程fork创建子进程,让子进程完成持久化操作(将当前redis数据库中的数据写入一个新的dump.rdb文件中,然后用这个新的dump.rdb文件替换老的dump.rdb文件),而主进程一直负责处理其他客户端的请求和命令
  • 通过save进行rdb快照:如果需要rdb快照,就直接在主进程中,往dump.rdb文件中写入数据, 此时不会触发子进程 & 文件替换逻辑~~

2. 自动触发

redis生成快照操作,不仅仅是手动执行命令才触发,也可以自动触发!!自动触发的方式/时机有下面三种:

1) 在 Redis 配置文件中,设置一下,让 Redis 每隔多长时间/每产生多少次修改就自动触发rdb快照

下图就是Redis 配置文件(redis.conf)中有关自动触发设置的配置
在这里插入图片描述

  • 其中save 900 1的意思就是

    • 以Redis 服务器上次执行持久化操作(RDB 快照保存)完成的时间点作为0时刻,如果在接下来的900s内,至少出现了一次更新操作,那么就在900s时刻执行一次rdb文件的更新。如果在接下来的900s内,并没有出现更新操作,那就将900s时刻作为0时刻重新计时900s
  • save 300 10的意思就是

    • 以Redis 服务器上次执行持久化操作(RDB 快照保存)完成的时间点作为0时刻,如果在接下来的300s内,至少出现了十次更新操作,那么就在300s时刻执行一次rdb文件的更新。否则就啥也不干,继续检测下一个300s内更新操作是否达到10次
  • save 60 10000的意思就是

    • 如果在上一次save存档之后的60s之内,发生了超过一万次的更新操作,那redis服务器就会在60s时刻重新存档一次,否则就啥也不干,继续检测下一个60s内更新操作是否达到10000次
  • save ""的意思是 关闭自动生成快照

对于redis来说,配置文件修改之后,一定要重新启动服务器,才能生效!!当然,如果想立即生效,也可以通过命令的方式修改,
虽然我们可以在redis.conf中自由修改配置。但是,此处修改上述数据的时候,要有一个基本原则:生成rdb快照,这个操作不能执行的太频繁!!

为什么生成rdb快照这个操作不能执行的太频繁呢?
因为生成rdb快照(更新dump.rdb文件)的本质,是将redis服务器中所有的数据(键值对)保存到磁盘中,这个操作是一个很大的工程,更新成本比较高,所以不能执行太频繁

2) redis服务器正常关闭时,就会自动生成rdb快照

什么叫redis服务器正常关闭?不正常关闭会咋样?

这里的正常关闭,包括但不限于:

  1. 通过shutdown命令(redis里的一个命令)关闭redis服务器
  2. bash命令行输入指令service redis-server restart/stop

如果是通过正常流程重新启动 redis 服务器,此时 redis 服务器会在退出的时候,自动触发生成 rdb 操作。

但是如果是异常重启(kill -9 或者服务器掉电),此时 redis 服务器来不及生成 rdb,内存中尚未保存到快照中的数据,就会随着重启而丢失

下图就是一个异常重启的操作举例
在这里插入图片描述

再看下图中的例子,redis中原本保存有key1~key3三个键值对,现在我再插入一个key4,不执行bgsave指令直接退出redis-server,请问再次登入redis服务器时,能够看到key4吗?为什么?
在这里插入图片描述
答案是可以,因为例子中的情况属于通过正常流程重新启动 redis 服务器,此时 redis 服务器会在退出的时候,自动触发生成 rdb 操作。

已知rdb文件新老版本生成时间的最短间隔是60s,我在中午12点钟进行了一次rdb文件的更新,然后我在接下来的1分钟内有超过10万次对redis数据库的更新操作,但是还没到12点01分,我服务器就挂了(异常退出),请问我重启服务器后,redis数据库是什么样的?
这个例子就属于服务器的异常重启(kill -9 或者服务器掉电),此时 redis 服务器来不及生成 rdb,内存中尚未保存到快照中的数据,就会随着重启而丢失~~
因为服务器在启动并恢复数据库的时候,读取的是12点save的rdb文件
所以数据库还是12点更新时候的样子,12点之后1分钟内的更新操作全部都不会被保存

3) redis进行主从复制的时候,主节点也会自动生成rdb快照,然后把rdb快照文件内容传输给从节点

如果把 rdb 文件故意改坏了,会咋样??如何处理这种情况?

实验:手动把rdb文件改坏,探究对redis服务器的影响

实验步骤

  1. vim dump.rdb,修改rdb文件
  2. 通过kill -9命令把redis-server进程给杀掉
  3. 通过service redis-server restart重启redis-server进程

实验过程

第一次实验,我们在dump.rdb末尾添加一串随机数据,最终发现好像没啥事儿,redis服务进程重启之后还是能够正常运行
第二次实验,我们将dump.rdb中间的一串数据删掉,通过service redis-server restart重启redis-server进程时发现进程启动不起来了
查看redis日志文件(所在路径为/var/log/redis/redis-server.log),日志文件中记录此次异常的信息如下,意思是服务器启动时在恢复redis数据这一块出现了问题

Short read or OOM loading DB. Unrecoverable error, aborting now.
Internal error in RDB reading function at rdb.c:2125 -> Unexpected EOF reading RDB file

实验总结

如果把 rdb 文件,故意改坏了,会咋样??
rdb文件是一个二进制文件,直接就把坏了的rdb文件交给redis服务器去使用,得到的结果是不可预期的:

  • 有可能一切正常
  • 也有可能redis服务器能启动,得到的数据是错的
  • 也可能redis服务器直接启动失败……

如何处理这种情况?

我们可以使用检查工具redis-check-rdb来检查一个dump.rdb文件格式是否正确,如果我们乱改了rdb文件,最后检查的结果一定是格式不正确

在redis5.0版本中,检查工具redis-check-rdb和redis服务器redis-server其实是同一个可执行程序(redis-server是可执行文件redis-check-rdb的一个符号链接/软链接)。我们可以在运行redis-server这个可执行文件的时候通过不同的参数选项,使用其中的不同功能
在这里插入图片描述

运行redis-server的时候,只要加入 rdb 文件作为命令行参数,此时就会以检查工具的方式来运行,去检测这个rdb文件格式是否正确,而不会真的启动 redis 服务器。运行起来之后,我们发现系统提示RDB ERROR DETECTED,说明这个rdb文件就是格式不正确
在这里插入图片描述

RDB策略总结

dump.rdb文件是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份、全量复制等场景,比如每6小时执行bgsave备份,并把RDB文件复制到远程机器或者文件系统中(如hdfs)用于灾备。

已知redis可以通过加载dump.rdb文件的方式恢复数据,也可以通过AOF方式恢复数据,请问这两种方式哪一种比较快?

Redis通过加载dump.rdb文件的方式恢复数据的速度远远快于AOF方式

  • 因为RDB 文件使用二进制的方式来组织数据。恢复数据时直接把数据读取到内存中,按照字节的格式取出来,放到结构体 / 对象中即可~~
  • 而AOF 是使用文本的方式来组织数据的,恢复数据时需要进行一系列的字符串切分操作~~

用RDB策略能否实现实时持久化、秒级持久化?

RDB没办法做到实时持久化、秒级持久化。因为bgsave每次运行都要执行fork创建子进程,属于重量级操作,频繁执行成本过高。

redis3版本的redis-server生成了一份rdb文件,请问该文件备份的数据能否用redis5版本的redis-server恢复出来?

RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个RDB版本,兼容性可能有风险——老版本的 redis 的 rdb 文件,放到新版本的 redis 中不一定能识别。
一般来说,实际工作中,redis版本都是统一的, 如果确实需要有一些“升级版本”的需求,确实需要升级,确实遇到了不兼容的问题,我们应该如何解决呢?(比如我公司要把redis的版本从3.0升级到5.0,但是因为老版本的rdb文件,新版本redis无法识别,那我更新之后,我的数据不会恢复不出来了吗?这咋办呢?)
这时候就可以通过写一个程序的方式,直接遍历旧的redis中的所有key,把数据取出来,插入到新的redis服务器中即可~~

rdb策略主要的缺点是什么?

RDB最大的问题,不能实时的持久化保存数据。 在两次生成快照之间,实时的数据可能会随着重启而丢失

AOF策略

AOF:即append only file,AOF策略主要的思想就是实时备份,即我做一次更新操作,就立即在操作之后进行一次备份

AOF的工作流程

在这里插入图片描述

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

实验:让redis用AOF策略实现数据持久化

AOF策略 默认一般是关闭不启用的状态,所以如果我们想用AOF策略,必须要先通过修改redis.conf配置文件,来开启AOF功能
在这里插入图片描述

上图是redis.conf中关于AOF策略的片段,可以看到这里默认是appendnoly no,表示关闭AOF,如果我们把它改成appendnoly yes,就开启AOF功能了

图最下方的appendfilename "appendonly.aof"意思是告诉我们aof文件名默认是appendonly.aof,该文件所在路径默认是/var/lib/redis,当然aof文件名与存放路径也都可以在redis.conf中修改
在这里插入图片描述

如果我们配置文件中既有save 60 10000这样的rdb策略配置,又开启了AOF功能,那此时redis的持久化策略究竟是哪一种呢?

  • 持久化阶段(redis服务器正常工作时):RDB 和 AOF 同时工作,分别生成各自的持久化文件。
  • 恢复阶段(redis服务器重启时):优先使用 AOF 文件恢复(若开启且有效),如果AOF文件无法使用,再使用 RDB文件恢复

开启AOF功能之后,我们重启服务器端,看看此时能不能实现数据持久化
在这里插入图片描述

  1. 原本redis数据库中没有数据,我现在插入两个数据,然后直接crtl +d退出redis客户端
  2. 接下来我在命令行中通过kill -9命令杀掉redis-server进程。之后再次查看后台进程,发现依然有redis-server进程,但是这个进程的pid和前面的已经不一样了,说明redis-Server进程被杀掉了之后,在后台自动完成了重启的操作
  3. 命令行输入redis-cli,进入redis客户端,再次查看redis的数据,发现刚刚插入的数据已经被成功保存了

aof文件介绍

aof文件就是使用AOF策略时redis形成的备份文件
aof文件类似于 mysql 的 bin log二进制日志,功能与dump.rdb相似,都是redis数据库的备份快照文件,当redis服务器重启时,就可以通过磁盘中的rdb文件或者aof文件,将数据库中所有信息恢复到服务器的内存中

aof文件与rdb文件的对比

  • 相同点:
    • 功能相同,都是用于redis数据恢复的快照文件
    • 文件路径相同,都是位于/var/lib/redis/文件目录下(都是存放在磁盘中的
  • 不同点:
    • rdb文件是二进制文件,而aof文件是文本文件

rewrite重写机制

启用AOF策略之后,redis会把用户的每个操作,都记录到aof文件中,每个操作都记录,那这个aof文件得多大啊,太大了那不是也很费劲吗?
确实,所以为了避免aof文件过大,redis就存在一个机制:rewrite重写机制,这个机制能够针对aof文件进行 整理 操作。所谓的整理就是剔除其中的冗余操作,并且合并一些操作,达到给aof文件瘦身这样的效果。由于整理之后,相当于给aof文件做了一次重写,所以就被叫做rewrite重写机制

啥叫重写机制?为啥要重写?咋重写的?

举个例子,比如说我创建一个键值对,后面又删了,那我再整理的时候,这两个操作互相抵消,就不整理了。我创建了一个键值对<key , 111>,然后我把Key的value又手动改成了222,过一段时间我又把Key的value又手动改成了333,最终我整理重写的时候,只会保留< key , 333>这个键值对,并不会记录前面两个中间值,也不会记录这个更改的过程

AOF策略的内存缓冲区

Redis虽然是一个单线程的服务器,但是速度很快——为啥速度快?重要原因是因为 用内存作为存储器——引入AOF之后,又要写内存,又要写硬盘,还能和之前一样快吗??

实际上,是没有影响的!!! 引入AOF并没有影响redis处理请求的速度~~这是因为:

  1. AOF机制并非是直接让工作线程把数据写入硬盘,而是先写入一个内存中的缓冲区,每次都等缓冲区充满之后,再统一写入硬盘,这样就大大降低了写硬盘的次数
  2. AOF是每次把新的操作写入到原有文件的末尾. 属于顺序写入。顺序写入磁盘的速度不是特别慢(比随机写入快多了)

综上所述,因为有内存缓冲区在内存和磁盘中充当缓冲,再加上是顺序写入磁盘,所以这个实时备份其实也比较快

什么是顺序读写,什么是随机读写?这两种读写方式谁比较快?
  • 在顺序读写的过程中,你下一个要读写的数据,和你刚刚读写完成的数据,在硬盘上的物理存储位置始终是相邻的(逻辑上相邻的数据在物理存储上也相邻)
  • 在随机读写的过程中,你下一个要读写的数据,和你刚刚读写完成的数据,在硬盘上的物理存储位置很大概率是不相邻的(逻辑上相邻的数据在物理存储上不相邻)

在硬盘上读写数据,顺序读写的速度比较快(但还是比内存读写速度慢很多),随机访问的速度比较慢(因为每次读完一个扇区,就大概率需要移动一次磁头)

  • 写硬盘的时候,一次读写硬盘数据的大小,对于性能影响不大
    一次传1B和100B,都是要经过寻道时延、旋转时延和传输时延,传输的数据大小仅仅会影响传输时延。
  • 但是读写硬盘的次数对于性能就有很大的影响。
    每传一次数据 ,都要经历一次完整的寻道+旋转+传输时延,每多传一次,就多一次寻道+旋转+传输时延

举例子:假设100个请求,100个请求的数据,一次写入硬盘, 比分100次,每次写入一个请求就快很多!!!嗑瓜子每氪一个,就把磕完的瓜子扔到垃圾桶,显然要比将磕完的所有瓜子一齐扔到垃圾桶要慢

AOF 缓冲区刷新策略

为什么要有AOF 缓冲区刷新策略?

把数据写入到缓冲区里,本质还是在内存中呀~~万一我还没把缓冲区中的数据刷新到磁盘中呢,进程就挂了,或者主机掉电了,咋办???是不是缓冲区中的数据就丢了???
是的!!!缓冲区中没来得及写入硬盘的数据是会丢的!所以我们要合理地选择缓冲区的刷新策略,不能让它长时间不刷新,导致掉电数据丢失,也不能让它刷太快,导致性能的浪费
没有完美的算法,也没有完美的策略,任何优秀的性能都是要付出代价的,归根结底最后都是一个取舍的问题。redis 给出了一些选项,让程序猿,根据实际情况来决定缓冲区的刷新策略!下面我们就来一一介绍

如何修改redis的 AOF 缓冲区刷新策略?

(1)永久修改

  1. 首先打开redis.conf文件
  2. 找到 appendfsync 选项,做如下修改
# appendfsync always
appendfsync everysec
# appendfsync no

上图中是将redis的 AOF 缓冲区同步文件策略修改为appendfsync always

  1. 重启redis
# 停止 Redis
redis-cli shutdown
# 启动 Redis(指定配置文件)
redis-server /path/to/redis.conf
  1. 验证配置是否生效
    通过 redis-cli 连接 Redis 后,使用 config get appendfsync 命令查看当前生效的策略:
redis-cli
config get appendfsync
1) "appendfsync"
2) "everysec"  # 显示当前启用的策略

(2)永久修改
若需临时修改(无需重启 Redis),可使用 config set 命令:

redis-cli config set appendfsync always  # 临时改为 always 策略

下面我们就来具体介绍一下redis的 AOF 缓冲区同步文件策略都有哪些

预备知识:linux系统调用write与fsync

系统调用write的作用是将数据从内存写到内核缓冲区中,这个不多介绍。下面我们重点要介绍一下系统调用fsync
你的理解其实并不矛盾,fsync 既是一个会让进程阻塞的系统调用,也是一个强制刷新缓冲区数据到磁盘的函数,这两个特性是统一的:

  1. fsync 的核心功能:强制刷盘
    fsync 的核心作用是将指定文件的内核缓冲区数据(包括用户态缓冲区和内核缓冲区)强制同步到磁盘

    • 对于应用程序来说,调用 write 等函数后,数据通常只是先写入内核的页缓存(内存缓冲区),而非不是直接写入磁盘(为了提升性能)。
    • fsync(int fd) 会强制求内核立即将该文件在内存中的所有脏数据(包括修改过的页缓存)写入物理磁盘,并等待磁盘 I/O 操作完成。
  2. fsync 的阻塞特性:等待刷盘完成

    • 正因为 fsync 需要确保数据真正正写入磁盘,所以它是一个阻塞调用
    • 调用 fsync 后,进程会进入阻塞状态,直到内核确认数据已经成功写入磁盘(或返回错误)才会继续执行。
    • 这种阻塞是为了保证“强刷”的可靠性——只有确认数据落盘,才能返回,避免程序误以为数据已持久化而继续执行(比如后续操作依赖该数据的安全性)。

以 Redis 的 AOF 持久化为例:

  • appendfsync always 时,每次写命令都会触发 fsync
    1. Redis 将命令写入 AOF 缓冲区(用户态)。
    2. 调用 fsync 强制将缓冲区数据刷入磁盘。
    3. fsync 完成前,Redis 进程会阻塞,不处理新的请求。
    4. 只有确认数据已写入磁盘,fsync 才返回,Redis 继续处理后续命令。

appendfsync always

  • 机制:每执行一条指令,就刷新一次缓冲区
    每次执行一条命令(如 set、hset 等),就立即将 AOF 缓冲区中的数据同步到磁盘的 AOF 文件中(调用系统 fsync 函数强制刷盘)。

  • 特点:

    1. 数据安全性最高: 任何写操作都不会丢失(除非磁盘故障)。
    2. 性能最差:每一次写命令都触发磁盘 IO,频繁的刷盘操作会严重影响 Redis 吞吐量,适合对数据安全性要求极高的场景(如金融交易)。

在always策略中,命令写入 aof_buf 后调用 fsync 同步,完成后返回(每往缓冲区中写一次,就立即刷新一次缓冲区,将刚刚写入缓冲区中的数据刷新到磁盘中)
这个选项对应的刷新策略,缓冲区刷新频率最高,数据可靠性最高,性能最低(耗时最长)

appendfsync everysec

  • 机制:每秒刷新一次
    每秒将 AOF 缓冲区中的数据同步到磁盘一次(由后台线程执行 fsync)。
  • 特点
    • 数据安全性:最多丢失最近 1 秒内的写操作(若 Redis 崩溃或机器宕机)。
    • 性能较好:每秒一次的刷盘操作对性能影响较小,兼顾了安全性和吞吐量,是大多数场景的推荐配置。

在everysec策略中,命令写入 aof_buf(aof缓冲区) 后只执行 write 操作,不进行 fsync。每秒由同步线程进行 fsync(你那边反正就往缓冲区中一直写,我这边缓冲区1s刷新一次)
中不溜秋的选项,刷新频率和性能都适中

appendfsync no

机制: Redis 不主动触发刷盘,仅将 AOF 缓冲区的数据写入操作系统的内核缓冲区,由操作系统决定何时同步到磁盘(通常依赖操作系统的页缓存刷新机制,如默认 30 秒一次)
特点:

  • 数据安全性最低:若操作系统崩溃,可能丢失大量数据(取决于操作系统何时刷盘)。
  • 性能最好:Redis 无需等待磁盘 IO,写操作仅在内存中完成,适合对性能要求极高、可容忍数据丢失的场景(如非核心缓存)

在no策略中,命令写入 aof_buf 后只执行 write,由 OS 控制 fsync 频率(你那边反正就往缓冲区中一直写,我这边缓冲区10s刷新一次)
这个选项对应的刷新策略,缓冲区刷新频率最低,数据可靠性最低,性能最高(因为不用刷新那么多次,所以运行起来效率比较高)

刷新频率越高,性能影响就越大
=> 服务器运行起来就越慢,同时数据的可靠性就越高.

刷新频率越低,性能影响就越小
=>服务器运行起来就越快,数据的可靠性就越低~~

AOF文件重写

什么时候会发生AOF文件的重写?为什么要重写?

随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。

是不是指令先从aof缓冲区刷到磁盘的aof文件中,然后我们再对磁盘中的aof文件进行重写?

并不是,在 AOF 重写过程中,新接收的写指令会同时写入两个地方

  1. 正常的 AOF 缓冲区(按 appendfsync 策略同步到旧 AOF 文件)
  2. AOF 重写缓冲区(专门记录重写期间的新指令)

这一机制确保了重写过程中数据不丢失,具体流程如下:

  • 重写开始前:Redis 仅将指令写入 AOF 缓冲区,最终同步到旧 AOF 文件。
  • 重写进行时
    • 新指令先写入 AOF 缓冲区,按原有策略同步到旧 AOF 文件(保证旧文件始终是完整的)。
    • 同时,新指令会被复制一份写入重写缓冲区(避免重写期间的新数据被遗漏)。
  • 重写完成后
    • Redis 将重写缓冲区中的所有指令追加到新 AOF 文件(确保新文件包含完整数据)。
    • 原子性地用新文件替换旧文件,后续指令仅写入新 AOF 文件和其缓冲区。

这种“双写”机制既保证了重写期间旧文件的可用性,又确保了新文件最终包含全部数据,是 Redis 保证 AOF 重写数据完整性的关键设计。

AOF重写操作的触发方式

手动触发:调用 bgrewriteaof 命令

如果在执行 bgrewriteaof 的时候,当前 redis 已经正在进行 aof 重写了,会咋样呢??
此时 bgrewriteaof命令不会再次执行 aof 重写,而是直接返回

如果,在执行 bgrewriteaof 的时候,发现当前 redis 在生成 rdb 文件的快照,会咋样呢?
此时,aof 重写操作就会等待,等待 rdb 快照生成完毕之后,再进行执行 aof 文件的重写

自动触发:根据 auto - aof - rewrite - min - size 和 auto - aof - rewrite - percentage 参数确定自动触发时机。

auto - aof - rewrite - min - size:规定触发重写时 AOF 的最小文件大小,默认为 64MB。
auto - aof - rewrite - percentage:代表当前 AOF 占用大小相比较上次重写时增加的比例。

AOF重写的流程

在这里插入图片描述

主要思路依然是主进程fork创建子进程,让子进程具体负责重写aof文件,自己则继续负责监听并处理客户端发来的请求

子进程重写aof文件的过程
  • 子进程只需要把内存中当前的数据,获取出来,然后对其中的数据进行整理,将最终整理的结果以AOF的格式写入到一个新的AOF文件中!!!(此时内存中的数据的状态,已经是AOF文件整理后重写的模样了)
  • 此处子进程写数据的过程,非常类似于RDB生成一个镜像快照
    • 只不过RDB这里是按照二进制的方式来生成的
    • 而AOF重写则是按照AOF这里要求的文本格式来生成的.
    • 这俩目的都是为了把当前内存中的所有数据状态记录到文件中!!
父进程在fork完子进程之后,还需要继续监听并处理客户端发来的新的请求。根据AOF策略,这些请求也需要同步到aof文件中,但是此时子进程正在对aof文件进行重写操作,这种情况下父进程应该如何处理呢?

在创建子进程的过程中,子进程会继承父进程的代码区和数据区。所以子进程地址空间中的aof文件,在没有修改之前,和父进程是同一个文件。一旦父子进程中的一方对aof文件做了修改,那就会触发写时拷贝机制,修改的那一方会在自己的进程地址空间中将aof文件复制一份,在这个新的aof文件中进行修改操作

所以父进程在收到新的请求之后,会:

  1. 照常将这些请求先写入内核缓冲区aof_buf中,然后等待缓冲区满了之后,将缓冲区中的数据写入磁盘中的AOF文件中。注意:当父进程成功将新请求同步到AOF文件中之后,此时父进程看到的AOF文件与子进程看到的AOF文件已经不是同一个文件了
  2. 父进程还会将新请求同步写入到另一个内核缓冲区aof_rewrite_buf(重写缓冲区)中,这个缓冲区专门用来存放父进程fork之后新接收到的请求(前面的内核缓冲区aof_buf中可能还存有fork之前的请求)

等到子进程完成aof文件的重写之后,会通过 信号 通知父进程,父进程再把 aof_rewrite_buf 缓冲区中的内容也写入到 新 AOF 文件里

最后用子进程地址空间中新的 AOF 文件代替父进程地址空间中旧的 AOF 文件

AOF策略与RDB策略对待父进程fork 之后的收到的新请求/新数据,处理方式有何不同?
  • rdb 对于 fork 之后到来的新数据,就直接置之不理了,并不会将fork之后到来的新数据记录到此时正在备份的rbd文件中,这部分数据会在下次定期备份时再备份到rdb文件中
  • 而aof 则对于 fork 之后的新数据,采取了 aof_rewrite_buf 缓冲区的方式来处理,从而使得aof重写结束时,可以让 fork 之后到来的新数据也同步到此次重写的aof文件中

rdb 本身的设计理念,就是用来 “定期备份” 的.,只要是定期备份,就难以和最新的数据保持一致~~
而aof 的理念则是实时备份~~而实时备份,就一定更费事,更耗资源
但是由于现在的系统中,系统的资源一般都是比较充裕的——所以此时aof 的开销也不算事——一般来说,aof 的适用场景更多一些的~~
即使如此实时备份不一定就比定期备份更好,还是要看实际场景的

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

有意义,主要是为了防止子进程那边备份失败
只要父进程在子进程重写的同时,还在继续写这个即将消亡的旧的 aof 文件,即使如果子进程那边突然失败了,父进程这边可以完全不受影响,只需要下次重新找个时间去fork子进程重写即可

考虑到极端情况~~假设在重写过程中,重写了一半了,服务器挂了 ,子进程内存的数据就会丢失,新的 aof 文件内容还不完整,此时如果父进程不坚持写旧 aof 文件,重启就没法保证数据的完整性了~~反过来说,此时如果父进程坚持写旧aof文件,父进程这边可以完全不受影响,只需要下次重新找个时间去fork子进程重写即可

举例子:跳槽
同学在 A 公司,干了一段时间之后,想跳槽,去 B 公司。
面试完了,offer 也发了
A 公司这里有一个交接时期
对于很多人来说,在 A 的最后一段时间里,就不好好干活了
在 A 这里没好好干,眼看要到了入职的时间,B 公司把 offer 给毁了。此时在 A 公司也干不下去了

redis的混合持久化

实验:混合持久化机制验证

在redis数据库中做如下操作
在这里插入图片描述

打开aof文件
在这里插入图片描述

vim appendonly.aof
在这里插入图片描述

查看当前aof文件信息
在这里插入图片描述

redis客户端命令行输入bgrewriteaof指令
在这里插入图片描述

之后再vim appendonly.aof,发现文件变成这样了,请问为啥这个aof文件重写之后,长得这么像前面的rdb文件?
在这里插入图片描述

因为这时候redis开启了混合持久化方式,这个方式一旦开启,在触发aof重写之后,redis就会把当前内存中的数据库按照rdb的二进制格式写入到新的aof文件中

什么是redis的混合持久化?

AOF本来是按照文本的方式来写入文件的。但是文本的方式写文件,后续加载的成本是比较高的,为了节省成本,redis引入了“混合持久化”的方式。开启“混合持久化”的方式之后,redis会按照rdb文件的二进制的来重写aof文件,这就大大节省了空间

如何开启混合持久化?

在redis.conf中,搜索aof-use-rdb-preamble,将配置改成aof-use-rdb-preamble yes,然后保存退出redis.conf文件,重新启动redis服务器(命令行输入service redis-server restart

开启混合持久化后,重写的aof文件变成了二进制格式,那后续新的操作请求/数据到来时,AOF策略不还是要进行实时备份吗?那就要对AOF文件进行追加写操作,请问这时候追加写的数据,是二进制格式,还是文本格式呢?

只有重写时会变成二进制格式,同步追加写依然是文本格式
在触发aof重写之后,就会把当前内存的状态按照rdb的二进制格式写入到新的aof文件中。后续再进行的操作,仍然是按照aof文本的方式追加到文件后面~~
在这里插入图片描述

可以看看上图,上面横着的就是aof重写之后的二进制数据,下面竖着的就是新追加写入的文本数据

当 redis 上同时存在 aof 文件和 rdb 文件的时候,此时redis服务器启动时,会用哪个文件来恢复数据?为什么?

可以看下面的流程图,只要aof功能开启了,redis服务器一定是优先去加载aof文件,找不到aof文件时,才去加载rdb文件
为什么优先aof文件?因为aof文件的实时性更强,文件数据更全

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值