暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis 持久化机制

阿东编程之路 2022-12-18
224

Redis 一般用作缓存,当 Redis 宕机后,内存中的数据将会丢失,我们可以从 持久层 数据库恢复,但是这种方式效率太低,而且还可能会出现缓存雪崩的情况。此外有些场景下我们需要保证缓存的数据不丢,比如而我们在大促等高并发场景下,会使用异步回写(write-behind)模式来合并写请求,而这些缓存数据是不能丢的。Redis 有一套自己的持久化机制,今天我们就来介绍下 Redis 的持久化机制。

一. AOF


AOF 全称:Append Only File,工作流程很简单:在执行完命令后(数据写入内存),记录一条日志。这种模式的日志被称为写后日志


为什么 AOF 机制不选择「写前」的模式呢?


1. MySQL 在写缓存和日志之前就已经将 SQL 语法校验过,且 InnoDB 的 redo log 是物理日志(在某个数据页上修改了哪些数据);Redis 的 AOF 日志里记录的是命令,而 Redis 为了避免额外检查的开销,选择先执命令,执行失败的命令就不记录日志,执行成功才会记录,这样就会避免记录错误的命令日志。


2. 虽然记录 AOF 的操作是主线程执行的,但是在写后记录日志不会阻塞当前的写操作。

虽然 AOF 是写后日志不会阻塞当前命令,但是也有两个问题:


  • 数据丢失:当刚执行完一个命令,还没来得及记录日志就发生宕机,数据就会丢失。

  • 性能影响:AOF 日志的执行也是在主线程里,势必对吞吐量会有影响。


AOF 三种回写策略

为了有针对性的解决「数据丢失」「性能影响」问题,Redis 提供三种 AOF 回写策略:Always、Everysec、No;Linux 操作系统中为了提升性能,使用了页缓存(page cache),在执行完 Redis 命令后,Redis 会将日志先写入叫做 page cache 的内存缓冲区,当调用操作系统内核的 fsync 方法时才会真正写入磁盘,而 AOF 的这三种写回策略决定着 fsync 的时机


  • Always:同步写回,每个命令执行完写入 page cache 后,立刻调用 fsync 方法写入磁盘;这种模式可靠性比较高数据基本不会丢失,但每次有写命令都会同步到磁盘,会影响 Redis 整体的吞吐量;


  • No:操作系统控制写回时机,命令执行完后,只是把命令写入 AOF 文件的内存缓冲区(page cache),由操作系统控制何时落盘;这种模式性能较好,同步磁盘的操作交给操作系统后台线程,一般为 30 s,同步时机不可控,丢失的数据量也难以估计,数据非常不安全;


  • Everysec:每秒写回,命令执行完后,先把日志写到 AOF 文件的内存缓冲区,然后每隔一秒将缓冲区的内容同步到磁盘。这种模式算是 Always 和 No 两种模式的折中方案:每秒落盘一次,避免了同步写回的开销,同时保证最多只丢失一秒的写命令;一般推荐使用该种模式控制 AOF 的写回时机。


如果需要保证数据的高可靠性,就选择 Always;如果需要高性能,可以选择 No;如果业务能容忍数据丢失一些同时又想要性能,可以选择性能与可靠的折中方案:Everysec。

AOF 重写


随着时间的推移,AOF 文件会越来越大,当文件过大就会导致一些性能问题:


1. 文件过大后,再往里追加记录的话效率会变低,整体吞吐量也会受到影响


2. 发生宕机后,AOF 持久化模式下 Redis 的恢复是需要从头到尾执行整个 AOF 文件的,文件越大恢复速度也会越慢


Redis 提供重写机制来解决 AOF 文件过大的问题。


AOF 重写就是根据缓存中的每个数据生成对应的写命令,比如某个 String 类型的键值对为 「key1 -> value1」,对应生成一个写命令「set "key1" "value1"」 到 AOF 文件中;原来的 AOF 文件中肯定会有多个命令对应一个键值对,但只有最后一个有效(最新),所以重写就会最大程度“压缩” AOF 文件。

AOF 重写过程


由于是对整个 Redis 的内存数据生成命令,这个过程会非常耗时,自然不会交给主线程做,当发生 AOF 重写时,Redis 会 fork 出来一个子进程来执行重写操作,避免阻塞主线程



AOF 重写逻辑主要分为两步,fork + 双写


1. 主线程首先 fork 出子进程,子进程复制父进程的虚拟页表;


2. 子进程开始读取内存中的数据,写到新的 AOF 重写日志中;


3. 子进程重写日志后,主进程还是会接收写命令,写命令除了会正常写入 AOF 旧文件的缓冲区,还会写到写到重写日志的缓冲区,保证重写的过程中发生宕机,旧的 AOF 日志还是全的;


4. 当子进程完成重写后,将重写缓冲区的增量命令刷新到重写日志文件中,保证新写入的数据不丢,增量日志的刷盘完成后就会将新的 AOF 文件代替旧文件了。


子进程为什么要拷贝父进程的虚拟页表而不是直接拷贝数据?


其实 fork 子进程这个过程是会阻塞主进程的,fork 采用写时复制Copy On  Write)机制,写时复制的思想就是当发生写的时候才去复制防止一次性拷贝大量数据造成主线程阻塞过长和内存浪费问题,主进程会拷贝一份虚拟内存页表虚拟内存地址和物理内存地址映射)给子进程,子进程就可以和主进程共享访问同一块物理内存区域,进行数据的读取重写工作;重写过程中如果主进程接收到某个 key 的写请求时,会拷贝这个 key 所在的内存页数据到新申请的内存页上,然后在新的内存页上进行更新,后续等到重写完成替换掉旧内存页。


AOF 优点:
1. 数据丢失的概率较小,适合数据安全性高的场景。
2. 由于 AOF 记录的是写命令,出现数据丢失可以直接通过 AOF 进行修复。

AOF 缺点:
1. AOF 文件会随着时间而变得冗余庞大,需要定期进行重写瘦身。
2. 由于 AOF 文件存储的是写命令,宕机后数据恢复过程会比较慢。
3. 如果使用 Always 的同步回写入策略会对性能造成影响。

二. RDB

RDB 全称 Redis Data Base,RDB 的持久化方式就是在指定的时间间隔内将内存中的数据以快照的形式(内存数据的二进制序列化形式)写入磁盘


RDB 生成策略


Redis 会根据配置自动进行 RDB 快照的生成,默认保存在 dump.rdb 的二进制文件中。而 RDB 生成策略配置格式如下:save t n,这个配置的语义是:当满足“在 t 秒内数据集发生 n 个改动”时就会触发一次 RDB 的生成;比如下面这个配置:

    save 10 100

    复制


    代表当 10 秒内有 100 次写入就触发一次 RDB 的生成。


    Redis RDB 模式持久化的默认生成策略如下,三个策略满足一个就会触发 RDB 生成:

      save 60 10000
      save 300 10
      save 900 1

      复制


      RDB 过程


      在满足配置自动进行 RDB 生成时,Redis 同样会 fork 出子进程来进行持久化(不阻塞主进程),也是采用 COW 写时复制机制,子进程将数据写入临时文件中,完成后替换旧的 RDB 文件;在生成 RDB 的过程中,主线程可以处理读写请求。



      RDB 优点:

      1. 直接存储内存数据的二进制序列化形式,占用磁盘更小。

      2. 宕机后重启恢复数据的速度比 AOF 快。


      RDB 缺点:

      1. 数据安全性较低,发生宕机时最多会丢失两次快照之间的命令。

      2. RDB 持久化的执行也是 Redis fork 出子进程执行,同样是重量级操作,频繁执行成本较高。


      三. 混合持久化


      我们会发现 AOF 和 RDB 两种方式各有优缺点,AOF 的数据安全性更高,但是需要定期瘦身,并且由于存储的是写命令宕机后的恢复速度比较慢(宕机重启后数据恢复的速度也很重要,因为数据恢复的过程中 Redis 相当于是不可用的);RDB 存储的是二进制序列化数据,恢复速度更快但是快照生成的频率不好控制,如果频率太低,两次快照间发生宕机,就会有较多的数据丢失,如果频率太高,又会有额外的开销(fork 和 IO 开销)。

      那有没有一种既有 AOF 的较高数据安全性也有 RDB 快速恢复数据能力的方式呢?


      Redis 4.0 提出了混合使用 AOF 和 RDB 的方式RDB 快照按照一定的频率生成,两次快照之间的增量命令记录到 AOF 日志中。当生成新快照时就会将增量的 AOF 日志清空。宕机重启后恢复数据直接读取 RDB 快照 + 少量的 AOF 增量日志即可。这种混合持久化的方式在有 RDB 快速恢复能力的同时还有着更高的数据安全性


      混合持久化的策略通过以下命令开启,官方建议如果需要持久化就用混合持久的方式。
        config set aof-use-rdb-preamble yes
        复制


        四. 总结

        本文介绍 Redis 两种基本持久化方式:AOF 与 RDB,使用 AOF 的数据安全性更高,但是宕机重启数据恢复速度较慢;RDB 的恢复速度快,但是快照生成频率不好控制;在 Redis 4.0 提出了混合使用 AOF 和 RDB 的方式,既有 AOF 的数据安全性 又有 RDB 的恢复速度,官方也建议如果需要持久化就用混合的方式。我们知道在 AOF 重写和 RDB 生成的时候,为了不阻塞主线程,Redis 采用了 写时复制(COW)机制,这种方式如果过程中出现大量写操作(内存页覆盖面较广)就会频繁出现写时复制,当此时内存不足就会发生 OOM,所以在使用 Redis 时,官方建议预留一部分的内存空间防止出现这个问题。


        如果觉得文章不错可以点个赞和关注


        参考:

        《Redis 核心技术与实战》作者:蒋德钧

        https://mp.weixin.qq.com/s/v4z9tUm46mUN4SxxMwwP6A

        https://www.zhihu.com/question/57045322

        https://redis.io/docs/management/persistence


        文章转载自阿东编程之路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

        评论