Redis——持久化

目录

RDB

手动触发

非阻塞式

阻塞式

自动触发

AOF

开启AOF

AOF文件

AOF

AOF三种策略

[always 策略](#always 策略)

[everysec 策略(默认)](#everysec 策略(默认))

[no 策略](#no 策略)

AOF文件的重写

重写流程

手动重写

自动重写

混合持久化


Redis持久化有两种策略:RDB和AOF。RDB是定期备份,AOF是每次修改都备份。RDB不保证安全,AOF可以保证相对安全

RDB

RDB有两种定期备份的途经,一种是执行redis命令手动触发,另一种是通过修改配置文件让redis自动定期备份。

手动触发

非阻塞式

相关命令:

sql 复制代码
//非阻塞式备份,期间可以执行其他命令
bgsave

执行流程:

redis使用新启动一个进程(而不是线程)的方式来进行后台备份。redis数据存放于内存,如果使用线程,备份线程和主线程访问同一块内存,此时需要加锁 、阻塞主线程。而使用进程的话,由于写时拷贝机制,调用bgsave时内存的状况会被完整保存,不会被中途修改,不需要进行加锁。所以使用进程是更加合适的

  • 执行 bgsave 命令时,Redis 父进程首先判断当前是否存在其他正在执行的子进程(如 RDB/AOF 子进程),若存在,则 bgsave 命令直接报告执行失败。(这是因为,多个bgsave同时执行没有意义,数据差异并不大,而且还消耗很多CPU、内存资源)
  • 父进程执行 fork 创建子进程,fork 过程中父进程会阻塞(但是备份的过程不会阻塞主进程)。
  • 父进程 fork 完成后,bgsave 命令返回 "Background saving started" 信息,不再阻塞父进程,父进程可继续响应其他命令,子进程开始备份。
  • 子进程创建 RDB 文件(存放于redis工作目录下,可通过配置文件修改),根据父进程内存生成临时快照文件,完成后对原有RDB文件进行原子替换。(为什么要这样?因为如果直接往原有RDB文件写入的话,一但这次bgsave被打断,原有的RDB文件就会被破坏,使得所有数据都无效)

RDB文件:

  • RDB文件里存放的是压缩过后的redis数据(压缩可以节省空间,但是复原需要消耗CPU),存储的直接是二进制
  • redis-check-rdb命令可以检查一个rdb文件是否正确(如果rdb文件是错误的,可能会导致数据错乱甚至服务器无法启动)。

阻塞式

sql 复制代码
//阻塞式备份,期间无法执行其他命令
save

自动触发

redis可以设置每隔多久或者每修改几次进行一次自动备份

通过配置文件修改自动触发的时机:

**当然,除了自己设置的触发时机,在服务器被正常关停等情况也会触发自动备份。**如果修改数据后没触发自动备份也没手动备份,并且redis服务崩掉了,那修改就会丢失。


AOF

一但AOF开启,redis服务器优先使用AOF文件进行数据恢复而不是RDB文件。

开启AOF

在redis.conf这个配置文件修改配置即可:

AOF文件

AOF文件以文本方式存放redis的操作记录,并不直接存二进制键值对。因此,使用AOF文件恢复数据要比RDB文件要慢很多。

每次写磁盘,就是在AOF文件末尾添加操作记录,不会修改文件中间的记录。正是因此,AOF模式下备份不需要经过"写到临时文件->然后再把原文件删除->给临时文件改名"这样的过程,就算redis服务突然崩了,也仅仅只是让某一条操作记录损坏,而不会影响前面所有的记录。

AOF有可读性好、容易修复(因为是命令文本)等优点。

AOF

AOF理论上是每一次修改都会写磁盘,但实际上:

  • 在AOF模式下,修改会首先被存入内存中的aof_buff缓冲区,一次时间循环后才会写磁盘。
  • AOF提供了三种策略,分别对应三种安全级别,安全性越高效率越低,具体如何选择取决于业务

AOF三种策略

always 策略

  • aof_buf 刷新时机
    每个写命令执行完毕后立即调用 flushAppendOnlyFile 函数,该函数会执行 write,将当前 aof_buf 中的所有命令写入内核缓冲区,然后清空 aof_buf
    ⏱️ 频率:每个写命令后一次。

  • 内核缓冲区刷新时机
    同一个调用过程中,write 之后立刻执行 fsync,强制将本次写入内核缓冲区的数据同步到磁盘。
    ⏱️ 频率:每个写命令后立即 fsync

结论:在 always 下,aof_buf 一有数据就被 write 并清空,且内核缓冲区立刻被 fsync,两者几乎同步发生,数据最安全,但性能最低。

everysec 策略(默认)

  • aof_buf 刷新时机
    Redis 主线程在每次事件循环的 beforeSleep 阶段(处理完一批客户端命令后,准备进入多路复用的等待前),都会调用 flushAppendOnlyFile。该函数将 aof_buf 中的所有数据通过 write 写入内核缓冲区,并清空 aof_buf
    只要事件循环在运行,这个动作就会非常频繁------可能每处理几个命令就发生一次,远高于每秒一次。
    ⏱️ 频率:每个事件循环批次一次(通常每毫秒级都可能发生)。

  • 内核缓冲区刷新时机
    flushAppendOnlyFile 中不会直接执行 fsync。而是由 Redis 专门启动的一个后台线程,每隔 1 秒钟调用一次 fsync,将这一秒内内核缓冲区中积累的 AOF 数据强制刷盘。
    如果主线程执行 write 时,发现上一次后台 fsync 尚未完成,为了不阻塞主线程,它甚至会跳过本次 write(即不写入内核缓冲区,aof_buf 不清空,等下次再写),以保证主线程的响应速度。
    ⏱️ 频率:后台线程每秒执行一次 fsync

结论:everysec 下,aof_buf 不断被快速清空并写入内核缓冲区(减少内存积压),但内核缓冲区的数据会积攒最多 1 秒,再由 fsync 批量刷盘。这是一种极好的平衡:性能较高,最多丢失 1-2 秒的数据。

no 策略

  • aof_buf 刷新时机
    everysec 完全相同:同样在事件循环的 beforeSleep 中调用 flushAppendOnlyFile,将 aof_buf 的内容 write 到内核缓冲区并清空 aof_buf
    ⏱️ 频率:每个事件循环批次一次。

  • 内核缓冲区刷新时机
    Redis 完全不调用 fsync。内核缓冲区的数据何时写入磁盘,完全由操作系统自行决定。
    在 Linux 系统中,通常会由内核线程 pdflush 定期(比如每 30 秒)或在脏页比例达到阈值时,将页缓存刷新到磁盘。
    ⏱️ 频率:操作系统决定(典型约 30 秒一次)。

结论:no 策略下,aof_buf 依然被频繁地 write 并清空,但内核缓冲区很可能长时间不刷盘,一旦宕机可能丢失最近数十秒的数据,性能最高,安全性最低。

修改redis.conf这个配置文件即可:

AOF文件的重写

这个主要是可以缩小AOF文件的体积,加快reids服务的启动时间,以及节省磁盘空间。缩小的方式就是等价(set key 111和del key 就等价于什么都不做)。 AOF文件的重写可以手动重写和自动重写。 实际上,AOF重写是直接把内存中的状态保存下来,并不会遍历原有的AOF文件进行等价缩小,这样太慢了。

重写流程

  1. 如果当前进程正在执行 AOF 重写,则新请求不执行(因为很短时间内多次重写没有意义)。如果当前进程正在执行 bgsave 操作,则重写命令延迟到 bgsave 完成之后再执行。(首先重写并不是特别重要知识一种优化,其次两个一同执行可能会导致资源消耗峰值更高,造成redis服务崩溃)

  2. 父进程执行 fork 创建子进程用于重写。

  3. 重写过程:

    1. 父进程 fork 之后,继续响应其他命令。所有修改操作同时写入两个缓冲区:常规 AOF 缓冲区(aof_buff)和 AOF 重写缓冲区(aof_rewrite_buff)。

    2. **子进程仅拥有 fork 时刻的内存快照,**根据内存快照,将数据转换为命令并合并写入临时 AOF 文件。

    3. 临时文件写入完成后,子进程发送信号通知父进程。

    4. 父进程将 AOF 重写缓冲区中临时保存的命令追加到临时文件末尾。

    5. 用临时文件原子替换旧 AOF 文件。

为什么父进程还要把修改写入aof_rewrite_buff缓冲区?

子进程进行aof重写的同时,父进程可以执行其他命令,但是子进程看不到这些修改,也就不会把这些修改对应的操作记录保存到临时文件中。那么如果原有AOF文件被临时文件替换,就会造成一部分的修改丢失,并且假设此时aof_buff因为数据都被写到旧AOF文件而清空,在后续的AOF写入中也不会把这部分修改存进去。但是重写被触发后会把这部分数据给填补上(因为重写是直接将内存数据转换成命令存放,并不依赖旧的AOF文件),但是一但断电或者服务崩掉,内存中的数据也丢失了,直接造成这部分数据永远丢失。任何AOF策略都不能保证不丢失,这不符合AOF的初衷。

把子进程重写期间父进程所做的修改存到aof_rewrite_buff缓冲区中,等到子进程重写成功后,父进程把****aof_rewrite_buff缓冲区的内容追加到临时文件末尾。在此期间,不会有命令被执行(redis单线程)。这就支持了AOF模式下的安全性。

为什么重写要涉及临时文件而普通备份则不?

很简单,重写会大范围修改AOF文件,影响太大。

手动重写

bash 复制代码
bgrewriteaof

自动重写

配置项 1:auto-aof-rewrite-percentage

  • 作用:触发重写的增长率阈值

  • 默认值:100

  • 说明:当前AOF文件体积比上次重写后增长了多少百分比时触发。设置为 0 可禁用自动重写。

配置项 2:auto-aof-rewrite-min-size

  • 作用:触发重写的文件大小下限

  • 默认值:64mb

  • 说明:避免在AOF文件还很小的时候就频繁重写。


混合持久化

就是在触发AOF重写后,会把当前内存中的数据按照RDB文件的那种二进制压缩格式来存放,以此来加快服务重启速度以及进一步缩小体积。而不是存放操作记录。

但是AOF下的普通备份仍旧是按照文本方式在AOF文件中追加操作记录(AOF备份比较频繁,如果AOF每次备份都直接存放二进制数据,那么必须使用临时文件保证大部分数据不会失效,但是这样效率会大大降低)。

在混合持久化情况下,AOF文件中可能会出现两种格式的内容: