简单谈谈 redis 持久化(RDB 和 AOF)

2022-09-04 11:25:54

1. 前言

1.1 为什么要持久化

  • redis的持久化功能主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。

  • 因为Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。

    为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

  • 除了RDB持久化,Redis还提供了AOF持久化(Append Only File),RDB持久化将当前数据保存到硬盘,而AOF则是将每次执行的写命令保存到硬盘。

1.2 怎么持久化

  • 当Redis服务重启时,利用读取持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件备份到其他服务器上。

2. RDB持久化

2.1 概述

  • RDB持久化是将内存种数据以快照的方式写入到二进制文件中,默认文件名是dump.rdb(配置文件中配置dbfilename dump.rdb),默认所生成的RDB文件是一个经过压缩(通过rdbcompression yes控制)的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。
    在这里插入图片描述

2.2 触发生成RDB文件

  • RDB持久化可以手动执行也可以根据服务器配置选项定期执行

2.2.1 使用save、bgsave命令手动触发

  • save:同步保存,阻塞主线程
    SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求。
    在这里插入图片描述

  • bgsave:fork新的子线程执行持久化,不阻塞主线程
    与 save 命令不同,bgsave 命令会创建一个子进程,由子进程来负责生成 RDB 文件,不会阻塞父进程处理其他调用它的请求,即:父进程(即Redis主进程)则继续处理请求。

  • 看 save 和 bgsave 的日志
    在这里插入图片描述

  • 需要注意的是:
    当执行 SAVE 命令时会阻塞 Redis 服务器进程,只有当 save 命令执行完、重新开始接受命令请求之后,客户端发送的命令才会被处理。

    在执行 bgsave 命令时,不会阻塞主线程,但是执行 bgsave 期间,客户端发送的save 命令和 bgsave 命令会被服务器拒绝。

    • save 命令不能和 bgsave 命令同时执行,为了避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生资源竞争条件。
    • 两个 bgsave 命令也会产生资源竞争条件,所以当一个 bgsave 在执行时,会拒绝其他 bgsave 命令的请求。

    在执行 bgsave 命令时,客户端发送的 BGREWRITEAOF 命令会被延迟到 BGSAVE 命令执行完毕之后执行;如果BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令会被服务器拒绝

  • ④ 根据上面我们了解到 save 命令和 bgsave 命令,由于 save 命令会造成服务器阻塞,所以实际开发中我们都是用 bgsave 命令来触发生成RDB文件实现持久化,但是手动触发肯定不是我们最理想的,所以一般是通过配置 save 选项,让服务器每隔一段时间自动执行一次BGSAVE 命令来实现自动触发生成RDB文件。请继续……

2.2.2 配置自动触发RDB机制

  • ① 先看默认配置
    在这里插入图片描述

  • ② 参数解释
    语法:save second times(其中 second是设置多少秒内,times 是设置在所设置的 second 内,redis数据库所发生变化的次数) ,比如:

    save 900 1 表示:900秒(15分钟)内数据发生一次变化;
    save 300 10 表示:300秒(5分钟)内数据发生10次变化;
    save 60 10000 表示:60秒(1分钟)内数据发生10000次变化;
    即用户可以通过 save 选项设置多个保存条件,在默认配置下,只要满足上面三种情况的任何一种情况,都会自动触发执行 bgsave 命令来生成 RDB 文件。

  • ③ 简单测试
    为了方便测试,我们调整一次默认值
    在这里插入图片描述
    满足60秒内修改3次的条件即自动触发,如下图:
    在这里插入图片描述

2.2.3 其他情况——服务器shutdown

  • 命令FLUSHALL 与 命令shutdown 也会触发生成RDB文件
    在这里插入图片描述

  • 需要注意的是:
    FLUSHALL 命令是清空数据库并执行持久化操作,所以FLUSHALL 之后的 RDB 文件是没有什么意义的,不能拿来恢复数据。
    FLUSHALL 不同的是,FLUSHDB 命令清空当前数据库且不执行持久化操作。所以FLUSHDB之后,如果想恢复数据可以杀掉redis-server的进程重启redis服务。

    shutdown 命令也会自动执行RDB持久化。

2.3 载入RDB文件

  • 在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入。
  • 怎么触发载入RDB文件?
    与触发生成RDB文件不同,载入RDB不需要手动或者配置来触发,没有专门用于载入RDB文件的命令,RDB文件的载入工作是在服务器启动时自动执行的,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件
  • 因为RDB文件是保存在硬盘里的文件,所以即使redis服务器进程退出,甚至运行redis服务器的计算机停机,甚至计算机失火永不可用,但只要远程备份的RDB文件存在,redis服务器就可以用它来还原数据库状态。

2.4 RDB对过期键的处理

  • 在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。

3. AOF(Append Only File)持久化

3.1 概述

  • redis还提供的另外一种持久化技术——AOF 持久化,AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态的,当 Redis 重启时读取并执行 AOF 文件中的命令来恢复数据。
    在这里插入图片描述

3.1.1 启动时载入还原数据

  • 我们先来说一下AOF如何还原数据的,再说AOF持久化过程。

  • 因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。具体怎么读入再重写,这个地方我们需要注意了:

    • 因为 Redis 的命令只能在客户端上下文中执行,而载入 AOF 文件时所使用的命令直接来源于AOF文件而不是网络连接,所以,服务器在载入 AOF 文件之前会先创建一个不带网络连接的伪客户端

      然后从 AOF 文件中分析并逐条读取出一条条写命令;每读取一条,服务器就使用这个伪客户端来执行从 AOF 文件中读出的写命令。

      等所有的命令被执行完后,AOF文件所保存的数据库状态就被完全恢复了。

    • 另外,伪客户端执行命令的效果和带网络连接的客户端执行命令的效果是完全一样的。

  • Redis读取AOF文件并还原数据库状态的图过程(来源《redis设计与实现》):
    在这里插入图片描述

3.2 AOF 持久化实现

3.2.1 开启AOF持久化

  • 默认是没有开启的,可通过appendonly 选项设置yes 来开启 AOF 持久化功能。

    修改配置之后记得重启 redis 服务,重启之后在 rdb 文件的同目录下会自动生成appendonly.aof 文件(这是默认文件名,可根据appendfilename 配置)。

    启动之后,appendonly.aof 文件不要删除,虽然 AOF 不需要触发 (文件是记录Redis的每条写命令,所以AOF不需要触发,会自动将命令写入),但是如果删除此文件,不会自动恢复!。
    在这里插入图片描述
    在这里插入图片描述

3.2.2 AOF 持久化实现的过程

3.2.2.1 AOF 持久化过程

  • AOF 持久化功能的实现包括如下:
    • ① 命令追加(append)

      当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf 缓冲区的末尾,而不是直接写入 aof 文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。

    • ② 文件写入(write) 和 文件同步(sync)

      根据服务器配置的appendfsync 选项的值,来决定是否需要将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件里面。

      为什么既有写入又有同步?
      是将命令写入到缓冲区的,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。但是这样虽然提高了效率,可能带来数据不安全问题(比如计算机异常停机等情况会导致缓存区里的内容还没来得及写入磁盘而造成数据丢失)。
      所以 redis 提供了同步机制(通过配置 appendfsync 控制),强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性。

3.2.2.2 关于 appendfsync 配置说明

  • 首先需要明白上面 AOF 持久化实现过程:
    首先先写入 aof_buf 缓冲区(新执行的命令也是先追加到 aof_buf 缓冲区的末尾),把每次执行新命令的整个过程看成一个事件(这整个事件包括将写命令追加到缓冲区,而已包括根据同步策略什么时候将缓冲区的内容写入到磁盘或者说同步到 aof 文件里)。
  • appendfsync 有三个可配值,appendfsync 的配置决定 AOF 持久化功能的效率和安全性,其中默认值是 everysec
    在这里插入图片描述
  • 关于 always 、everysec、no 的说明
    • always :将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件。
      即:每执行一个命令都会写入到缓冲区并同步到 aof 文件(写入磁盘),所以always 效率上是最慢的,安全性上是最安全的,即便异常停机,也只会失去最后一个操作的命令。
    • everysec:将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,操作由专门的线程每隔一秒对 AOF 文件进行同步一次。
      即:每隔一秒对 AOF 文件进行同步一次,同步频率上相比 always 低了很多,即便出现异常停机,只是丢失了一秒钟的命令数据,所以everysec 是默认配置,也推荐的配置,效率上足够快,数据安全也有保证
    • no :将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统来决定。
      即:命令写入aof_buf后调用系统write操作,不对AOF文件做同步操作,至于何时同步由操作系统控制;这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证

3.2.3 AOF 文件重写

  • 什么是 AOF 重写?AOF 重写解决什么问题?
    AOF 持久化是通过保存被执行的写命令来记录数据库状态的,所以随着时间的流逝,AOF 文件中的内容会越来越多,文件体积越来越大,那么使用 AOF 文件来恢复数据的时间就会越多,而且对计算机的存储也有一定的影响。所以为了解决 AOF 文件体积的膨胀问题, redis 提供了 AOF 文件重写功能(rewrite)。

    Redis服务器可以创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧两个文件所保存的数据库状态是相同的,但是新的 AOF 文件不会包含任何浪费空间的冗余命令,通常体积会较旧 AOF 文件小得多。

3.2.3.1 重写实现过程

  • 需要注意的是:
    AOF 重写会产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小
    虽然会产生新的AOF文件,但是AOF 重写并不需要对现有的 AOF 文件进行任何读取、分析或者写入操作,而是通过读取服务器当前的数据库状态来实现的。即:在现有数据库状态的基础上新建一个新的 aof 文件(如果有对一个键有来回操作的复杂命令,这些冗余的复杂命令不去整合它们,而只根据当前的数据库状态把写命令写入到新的 aof 文件中),后续再拿新的 aof 文件替换旧的 aof 文件。
    用命令bgrewriteaof 触发重写操作,如图:
    在这里插入图片描述
  • AOF 重写功能的实现原理,可理解为:从数据库中读取键现在的值,然后用一条命令去记录键值对来代替之前操作这个键值对的多条命令。
  • 再简单说一下重写过程
    • ① AOF 重写时开启一个子进程,不影响 redis 服务器继续接收客户端的命令。
    • ② 为了保证数据一致,在执行bgrewriteof 命令重写时,Redis 服务器会维护一个 AOF重写缓冲区,该缓冲区会在子进程创建 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓存区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。
    • ③ 最后,服务器用新的 AOF 文件替换旧的 AOF 文件来完成 AOF 文件重写操作。

3.2.3.2 AOF 重写触发方式

  • 两种触发方式:
    ① 通过bgrewriteaof 命令手动触发,上面我们有说。
    ② 通过配置自动触发
    auto-aof-rewrite-percentage100
    auto-aof-rewrite-min-size 64mb
    这个配置的含义是,当 AOF 文件大于 64MB 时,并且当前写入文件的大小超过上一次 rewrite 之后的文件大小的百分之100时也就是2倍时再次触发 Rewrite 。在这里插入图片描述

4. RDB 和 AOF 选择方案

4.1 RDB 和 AOF 优缺点对比

  • 体积上说:
    对于相同的数据集来说,AOF 文件的体积一般要大于 RDB 文件的体积。
    在这里插入图片描述
  • 从数据安全性来说:
    根据上面我们对 AOF 持久化的同步策略可以知道,AOF 可以控制到最多只丢失一个命令的数据,而 RDB 保存的是一个时间点的快照,如果 redis 服务器出现了故障,丢失的就是从最后一次RDB执行的时间点到故障发生的时间间隔之内产生的数据。所以 考虑到 RDB 可能会导致一定时间内的数据丢失,相对来说 AOF 更能保证数据的安全性。
  • 从加载文件上来说:
    RDB 只需要把相应数据加载到内存并生成相应的数据结构(有些结构如intset、ziplist,保存时直接按字符串保存,所以加载时速度会更快),而 AOF 文件的加载需要先创建一个伪客户端,然后把命令一条条发送给Redis服务端,服务端再完整执行一遍相应的命令,所以 AOF 的加载速度比 RDB 慢很多。
  • 效率上来说:
    由于AOF 文件较大会影响 redis 的启动速度,所以 RDB 效率高于 AOF。

4.2 RDB 与 AOF 优先级

  • 如果同时开启 RDB 和 AOF 持久化,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。
    在这里插入图片描述

4.3 混合持久化

  • 因为 AOF 和 RDB 各有优缺点,因此 Redis 一般会同时开启 AOF和 RDB。
    但这样会带来如下的两难选择:重启时如果优先加载 RDB,加载速度更快,但是数据不是很全;如果优先加载 AOF,加载速度会变慢,但是数据会比 RDB 中的要完整。

  • 所以为了能同时使用 RDB 和 AOF 的优点,redis增加了混合持久化:

    开启混合持久化:aof-use-rdb-preamble yes 默认是 yes 开启状态。

    在开启混合持久化的情况下,进行 AOF重写时子进程将当前时间点的数据快照保存为 RDB 文件格式,而后将父进程累积命令保存为 AO F格式。意思就是 AOF 重写时会把持久化数据先以 RDB 格式写入到 AOF 文件的开头,之后的数据再以 AOF 的格式追加到文件的末尾,数据存储结构如图:
    在这里插入图片描述
    加载时,首先会识别 AOF 文件是否以 REDIS 字符串开头,如果是,就按 RDB 格式加载,加载完 RDB 后继续按 AOF 格式加载剩余部分。

5. 参考书籍

  • 黄健宏著的《redis设计与实现》
  • 《redis5设计与源码分析》
  • 作者:@素素~
  • 原文链接:https://susu-math.blog.csdn.net/article/details/124500227
    更新时间:2022-09-04 11:25:54