Redis 持久化机制

104 阅读13分钟

什么是持久化

Redis 是内存数据库,会将数据库状态存储在内存里面,一旦服务器进程退出,服务器中的数据库状态也会消失,造成数据丢失。

我们需要将内存中的数据进行持久的存储,比如:写到磁盘、硬盘,避免数据的意外丢失。

为什么持久化

  1. 为了重用数据

    比如:机器重启、机器故障 之后需要恢复数据。

  2. 为了做数据同步

    比如:Redis 集群的主从节点通过 RDB 文件同步数据。

持久化方式有哪些

  1. RDB:快照
  2. AOF:追加文件
  3. RDB + AOF 混合使用

RDB 快照

什么是 RDB 持久化?

  1. 通过创建快照的方法,获取某个时间点上的内存数据的副本,

    • 可将该快照副本进行备份,复制到其他服务器从而创建具有相同数据的服务器副本。
    • 可将该快照保存在本地,以便重启服务器后使用。
  2. RDB 文件是一个经过压缩的二进制文件,可以通过该文件还原成 RDB 文件时的数据库状态。

  3. RDB 快照持久化,是 Redis 默认采用的持久化方式。

RDB 文件的创建和载入

创建

  1. Redis 提供了两个命令来生成 RDB 快照文件:

    • save:会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止(在服务器阻塞期间,不能处理任何命令的执行)才会重新开始接受命令请求。
    • bgsave:不会阻塞 Redis 服务器进程,会派生出子进程,由该子进程创建 RDB 文件,而主进程则可以继续处理任何命令请求。
  2. 创建 RDB 文件是由 rdb.c / rdbSave 函数完成的,两个命令会以不同的方式去调用该函数。

载入

  1. RDB 文件是在服务器启动时自动载入的,没有专门的命令,只要服务器启动时检测到有 RDB 文件,就会自动载入。

    ❕ 注意:

    服务器默认使用 RDB 文件来还原数据库状态的,如果服务器开启了 AOF 持久化能,那么就会优先使用 AOF 文件来还原数据库状态(这是因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高)。

  2. 载入 RDB 文件是由 rdb.c /rdbLoad 函数完成的。

  3. 载入期间,服务器会处于阻塞状态,直到载入完成。

save、bgsave、BGREWRITEAOF 命令

  1. SAVE 命令:在主进程上保存数据到磁盘上的一个快照文件中,执行期间,会阻塞 Redis 服务器进程,直到快照文件生成完毕。
  2. BGSAVE 命令:在子进程上保存数据到磁盘上的一个快照文件中。执行期间,不会阻塞 Redis 服务器的进程,因此在执行期间可以继续处理其他命令请求。
  3. BGREWRITEAOF 命令:在后台异步重写 Redis 数据库的 AOF(Append-Only File)日志文件,将 Redis 的写操作追加到日志文件中,也不会阻塞 Redis 服务器的进程,因此在执行期间可以继续处理其他命令请求。

❕ 注意:

BGSAVE 命令在执行期间:

  • 会拒接 SAVE 命令的请求,为了避免 父进程 和 子进程 同时执行两个 rdb.c / rdbSave 函数的调用,以防产生竞争条件。
  • 会拒接 BGSAVE 命令的请求,同时执行也会产生竞争条件。
  • 会延迟 BGREWRITEAOF 命令的请求,直到 BGSAVE 命令执行结束;相反 BGREWRITEAOF 命令执行期间,会拒接 BGSAVE 命令(主要是性能方面的考虑)。

BGSAVE 命令自动间隔保存

因为 BGSAVE 命令是在子进程上执行,不会阻塞服务器父进程,所以我们可以通过设置服务器配置的 save 选项,让服务器每个一段时间就自动执行一次 BGSAVE 命令。

redis.conf 配置文件中默认有此下配置:

 save 900 1           #在 900秒(15分钟)之内,至少对数据库修改了 1 次,Redis 则自动触发 BGSAVE 命令创建快照。
 ​
 save 300 10          #在 300秒(5分钟)之内,至少对数据库修改了 10 次,Redis 则自动触发 BGSAVE 命令创建快照。
 ​
 save 60 10000        #在 60秒(1分钟)之内,至少对数据库修改了 10000 次,Redis 则自动触发 BGSAVE 命令创建快照。

AOF 追加文件

什么是 AOF 持久化?

与 RDB 快照持久化相比,AOF 持久化的实时性更好。

AOF 持久化是通过保存 Redis 服务器所执行的写命令来记录数据库状态。服务器在启动时,通过载入和执行 AOF 文件中保存的命令来还原服务器关闭之前的数据库状态。

默认情况下 Redis 没有开启 AOF 持久化(Redis 6.0 之后已经默认是开启了),可以通过 appendonly 参数手动开启:

 appendonly yes

AOF 持久化的实现

分为三个步骤:追加(append)、文件写入、文件同步(sync)

命令追加

AOF 持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议的格式将该命令追加到服务器状态的 aof_buf 缓存区的末尾(AOF 缓冲区)。

文件写入 与 文件同步

服务器每次结束一个事件循环之前,都会调用 flushAppendOnlyFilel 函数,考虑是否需要将 aof_buf 缓冲区中的内容写入/保存到 AOF 文件里。

Redis 的服务器进程就是一个事件循环 (lop),这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像 servercron 函数这样需要定时运行的函数。

flushAppendOnlyFilel 函数的行为是由服务器配置的 appendfsync 选项的值来决定的:

appendfsync 选项的值flushAppendOnlyFilel 函数的行为
always在每次写入命令到 AOF 缓冲区后,Redis 会立即将 AOF 缓冲区的内容同步到 AOF 文件,并使用 fsync 命令强制将数据同步到硬盘上。
everysec(默认)Redis 会启动一个定时任务,每隔一秒钟将 AOF 缓冲区的内容同步到 AOF 文件,并使用 fsync 命令强制将数据同步到硬盘上。
noRedis 不会主动将 AOF 缓冲区的内容同步到 AOF 文件,而是依赖于操作系统的异步写入。这意味着可能存在一定的数据丢失风险。

另外,当 AOF 缓冲区的内容累积到一定程度时,Redis 也会将这些内容写入 AOF 文件。这个累积的阈值可以通过配置文件中的 auto-aof-rewrite-min-size 参数来设置。

AOF 文件的载入与数据还原

服务器重启后,只需要读入并重新执行一遍 AOF 文件里保存的的写命令,就可以还原服务器关闭之前的数据库状态。

AOF 校验机制

AOF 校验机制是 Redis 在启动时对 AOF 文件进行检查,以判断文件是否完整,是否有损坏或者丢失的数据。这个机制的原理其实非常简单,就是通过使用一种叫做 校验和(checksum) 的数字来验证 AOF 文件。这个校验和是通过对整个 AOF 文件内容进行 CRC64 算法计算得出的数字。如果文件内容发生了变化,那么校验和也会随之改变。因此,Redis 在启动时会比较计算出的校验和与文件末尾保存的校验和(计算的时候会把最后一行保存校验和的内容给忽略点),从而判断 AOF 文件是否完整。如果发现文件有问题,Redis 就会拒绝启动并提供相应的错误信息。AOF 校验机制十分简单有效,可以提高 Redis 数据的可靠性

AOF 重写

  1. 随着时间,AOF 文件的内容会越来越多,体积也越来越大,如果不进行控制,对会对 Redis 服务器、计算机造成影响,且还原数据所需的时间就越多。

  2. 所以,Redis 提供了 AOF 重写功能,也就是 Redis 服务器会创建一个新的 AOF 文件来替代现有的 AOF 文件,新旧文件所保存的数据库状态相同,但是新文件会去除任何浪费空间的冗余命令,新文件会比旧文件的体积更小。

  3. 实际上,AOF 文件重写(新)并不会对现有的 AOF 文件(旧)进行任何读取、分析或写入操作,而是通过读取服务器当前的数据库状态来实现的。

    例如:

     redis> RPUSH list "A" "B"   //["A""B"]
     (integer) 2
     ​
     redis> RPUSH list "C"       //["A""B""C"]
     (integer) 3
     ​
     redis> RPUSH list "D" "E"   //["A""B""C""D""E"]
     (integer) 5
     ​
     redis> LPOP list "A"        //["B""C""D""E"]
     "A"
     ​
     redis> LPOP list "B"        //["C""D""E"]
     "B"
    

    如果服务器要保存尽量少的命令,不是直接保存这 5 条命令,而是直接从数据库中读取键 list 的值,然后用 1 条命令来代替保存在 AOF 文件中的这 5 条命令

     redis> RPUSH list "C" "D" "E"   //["C""D""E"]
     (integer) 3
    
  4. 开启 AOF 重写功能:

    • 调用 BGREWRITEAOF 命令手动执行

    • 设置下面两个配置项,让程序自动决定触发时机:

      • auto-aof-rewrite-min-size:如果 AOF 文件大小小于该值,则不会触发 AOF 重写。默认值为 64 MB;
    • auto-aof-rewrite-percentage:执行 AOF 重写时,当前 AOF 大小(aof_current_size)和上一次重写时 AOF 大小(aof_base_size)的比值。如果当前 AOF 文件大小增加了这个百分比值,将触发 AOF 重写。将此值设置为 0 将禁用自动 AOF 重写。默认值为 100。

  5. AOF 重写由 aof_rewrite 函数产生新的 AOF 文件,并且 Redis 服务器会将 AOF 重写功能由子进程执行,父进程可以继续处理请求命令(是子进程不是子线程,所以可以保存数据的安全性)

    aof_rewrite 函数生成的新 AOF 文件只包含还原当前数据库状态所必须的命令,所以新 AOF 文件不会浪费任何硬盘空间。

  6. 子进程执行 AOF 重写期间,如果父进程有新的写命令,那么就会出现重写后的文件与当前数据库状态不一致。

    为了解决这个问题,Redis 服务器设置了一个 AOF 重写缓冲区:这个重写缓冲区会在创建子进程后开始使用,当父进程(Reids 服务器)执行完一个写命令,同时会将这个命令发送给 AOF 缓冲区和 AOF 重写缓存区。

  7. 当子进程的 AOF 重写工作完成之后,向父进程发送信号,父进程接受到信号后调用信号处理函数,并执行以下工作:

    • 将 AOF 重写缓冲区中的内容写入到 AOF 新文件,保持新 AOF 文件与当前数据库状态一致。

    • 对新的 AOF 文件重命名,覆盖现有的 AOF 文件,完成替换。

    这个信号处理函数执行期间会对父进程造成阻塞,只有执行完毕后,才可以继续接受命令请求。

AOF 记录日志

  1. 关系型数据库(如 MySQL)通常都是执行命令之前记录日志(方便故障恢复),而 Redis AOF 持久化机制是在执行完命令之后再记录日志。

    1)Redis 执行命令写内存 2)记录日志,写磁盘

  2. 为什么是在执行完命令之后记录日志呢?

    • 避免额外的检查开销,AOF 记录日志不会对命令进行语法检查;
    • 在命令执行完之后再记录,不会阻塞当前的命令执行。
  3. 带来的风险

    • 如果刚执行完命令 Redis 就宕机会导致对应的修改丢失;
    • 可能会阻塞后续其他命令的执行(AOF 记录日志是在 Redis 主线程中进行的)。

RDB 和 AOF 的混合持久化

Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble 开启)。

如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。

RDB 和 AOF 对比

RDB 比 AOF 优秀的地方

  • RDB 文件存储的内容是经过压缩的二进制数据, 保存着某个时间点的数据集,文件很小,适合做数据的备份,灾难恢复。

    AOF 文件存储的是每一次写命令,类似于 MySQL 的 binlog 日志,通常会比 RDB 文件大很多。当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。不过, Redis 7.0 版本之前,如果在重写期间有写入命令,AOF 可能会使用大量内存,重写期间到达的所有写入命令都会写入磁盘两次。

  • 使用 RDB 文件恢复数据,直接解析还原数据即可,不需要一条一条地执行命令,速度非常快。

    而 AOF 则需要依次执行每个写命令,速度非常慢。也就是说,与 AOF 相比,恢复大数据集的时候,RDB 速度更快。

AOF 比 RDB 优秀的地方

  • RDB 的数据安全性不如 AOF,没有办法实时或者秒级持久化数据。生成 RDB 文件的过程是比较繁重的, 虽然 BGSAVE 子进程写入 RDB 文件的工作不会阻塞主线程,但会对机器的 CPU 资源和内存资源产生影响,严重的情况下甚至会直接把 Redis 服务干宕机。

    AOF 支持秒级数据丢失(取决 fsync 策略,如果是 everysec,最多丢失 1 秒的数据),仅仅是追加命令到 AOF 文件,操作轻量。

  • RDB 文件是以特定的二进制格式保存的,并且在 Redis 版本演进中有多个版本的 RDB,所以存在老版本的 Redis 服务不兼容新版本的 RDB 格式的问题。

  • AOF 以一种易于理解和解析的格式包含所有操作的日志。可以轻松地导出 AOF 文件进行分析,也可以直接操作 AOF 文件来解决一些问题。比如,如果执行 FLUSHALL 命令意外地刷新了所有内容后,只要 AOF 文件没有被重写,删除最新命令并重启即可恢复之前的状态。

总结

  • Redis 保存的数据丢失一些也没什么影响的话,可以选择使用 RDB。
  • 不建议单独使用 AOF,因为时不时地创建一个 RDB 快照可以进行数据库备份、更快的重启以及解决 AOF 引擎错误。
  • 如果保存的数据要求安全性比较高的话,建议同时开启 RDB 和 AOF 持久化或者开启 RDB 和 AOF 混合持久化。