Redis持久化机制-AOF

Redis持久化机制-AOF

1. 概述

Redis很大的一个应用场景就是缓存,因为速度很快,通过将后端数据库中的数据存储在内存当中,然后直接从内存中读取数据。

但是这样做的一个问题,是如果服务器宕机,内存中的数据将会全部丢失掉。对于恢复数据,我们可能的解决方案是:

  • 从后端数据库访问
    • 对数据库的频繁访问会给数据库造成巨大的压力
    • 会导致应用程序响应速度变慢
  • 理念 - 不从后端数据库读取,实现数据的持久化
    • AOF日志
    • RDB快照

2. AOF日志的实现

2.1 什么是AOF

2.2 AOF记录了什么

  • 传统数据库日志

    • 记录修改后的数据
  • AOF

    • 写后日志

    • 记录Redis收到的每一条指令,这些命令以文本形式保存

    • AOF记录日志的时候,不会进行语法检查的! 因此,如果先记录日志,再做执行的话,日志当中就有可能记录错误的命令,在使用日志恢复数据的时候,就有可能出错

      • 还是对于速度和保证性的tradeoff

        AOF文件EG

        AOF日志范例

    • 写后日志可以避免出现记录错误命令的情况

    • 而且因为是在命令执行后才记录日志,所以不会阻塞当前的写操作

2.2.1 写后日志的风险

  • 如果刚执行完一个命令,还没有记录日志就宕机了,那么命令和相应的数据都有丢失的风险。
  • AOF虽然避免了对当前命令的阻塞,但是可能会给下一个操作带来阻塞风险
    • 因为AOF日志也是在主线程中执行,如果将日志文件写入磁盘的时候,磁盘写压力大,会导致写盘非常慢

解决方案: 需要控制写命令执行完成后AOF日志写回磁盘的时机

2.3 AOF文件载入与数据还原

  • 详细步骤
    • 创建一个不带网络连接的fake client
      • 因为Redis命令只能在客户端的上下文当中来执行
    • 从AOF文件当中分析并读取一条写命令
    • 使用伪客户端来执行被读出的写命令
    • 重复执行上述的读取和执行指令,直到处理完毕

3. 单点研究

3.1 写回策略

  • Redis的服务器进程是一个事件循环,这个循环当中的文件事件负责
    • 接收客户端的命令请求
    • 向客户端发送命令回复
  • 时间事件负责接收客户端的命令请求
def eventLoop(): 
    while true: 

        // 处理文件事件,可能会有新内容追加到aof_but缓冲区
        processFileEvents()

        processTimeEvents()

        flushAppendOnlyFile()
  • 可用的写回策略 - AOF当中的appendfsync的三个可选值

    • Always 同步写回

      • 每个写命令执行完,立刻同步将日志写回磁盘
    • EverySec 每秒写回

      • 每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒将缓冲区中的内容写入磁盘
    • No 操作系统控制的写回

      • 每个写命令执行完,只是将日志写到AOF文件的缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

      appendfsync 规定的写回策略

      写回策略对比

  • 写回策略的选择 — 根据对于性能和可靠性的要求,来选择选用哪一种写回策略

    • 想要获得高性能,选用No策略
    • 想要高可靠性的保证,选用Always策略
    • 如果允许数据有一点丢失,又希望性能不受太大的影响,选用EverySec策略

3.2 如何处理过大的日志文件 — AOF重写机制

日志过大会产生性能问题,主要在以下三个方面:

  1. 文件系统本身对文件大小的限制,无法保存过大的文件

  2. 如果文件太大,再向里面追加命令记录,效率会降低

  3. 如果发生宕机,AOF中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程会非常缓慢,这就会影响到Redis的正常使用

3.2.1 重写可以优化日志大小的原理

  • AOF重写机制

    • 重写的时候,根据数据库现状创建一个新的AOF文件

      • 读取数据库所有的键值对
      • 针对每一个键值对用一条命令记录它的写入
      • 需要回复的时候,直接执行这条命令
    • 重写可以使得日志文件变小,因为可以压缩多条指令到一条

      • 即AOF日志是用来做恢复的,我不需要记录每一步的中间状态,只要知道最终对应的key的value是多少就好

        AOF重写E.G

        重写原理

3.2.2 重写如何避免阻塞?

  • AOF日志由主线程写回 — 是在执行了主操作以后,直接call的AOF的方法来进行执行的,而重写过程是由后台子进程bgrewriteaof来完成的,是为了避免阻塞主线程,导致数据库性能的下降

  • 重写的整个流程

    • 一处拷贝

      • 每次执行重写的时候,主线程fork到bgrewriteaof子进程
      • 主线程的内存会被拷贝一份到bgrewriteaof子进程当中,其中会包含数据库的最新数据
      • 然后该子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志
    • 两处日志

      • 主线程当中的AOF日志

        • 但有新的操作进入,Redis会将该操作写到AOF日志缓冲区
        • 这样即使宕机,AOF日志的操作仍齐全,可以用来做恢复
      • AOF重写日志

        • 该操作同时也会被写入到重写日志的缓冲区

        • 等到拷贝数据的所有操作记录重写完成后,重写日志记录的这些最新操作也会写入新的 AOF 文件,以保证数据库最新状态的记录。此时,我们就可以用新的 AOF 文件替代旧文件了。

          AOF重写缓冲

      • 如何解决在AOF重写过程当中数据库新的更新导致的服务器当前的数据库状态和重写后AOF文件所保存的数据库状态不一致的问题??

        • AOF重写缓冲区会在服务器创建了子进程之后开始使用
        • 当redis服务器执行一个写命令之后,它会同时将其发给AOF缓冲区还有AOF重写缓冲区
        • 当子进程完成了AOF重写工作之后,会向父进程发出信号,父进程接收到以后,会调用一个信号处理函数,而后:
          • 将AOF重写缓冲区中的所有内容写入到新AOF文件里
          • 对新的AOF文件改名,覆盖现有的AOF文件,完成新旧两个AOF文件的替换

3.3 AOF 日志重写过程当中的阻塞风险

  • Fork子进程的过程
    • fork并不会一次性拷贝所有内存数据给子进程,采用的是操作系统提供的copy on write机制
      • copy on write机制就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞的问题
        • fork()之后,kernel把父进程中所有的内存页的权限都设为read-only,然后子进程的地址空间指向父进程。当父子进程都只读内存时,相安无事。当其中某个进程写内存时,CPU硬件检测到内存页是read-only的,于是触发页异常中断(page-fault),陷入kernel的一个中断例程。中断例程中,kernel就会把触发的异常的页复制一份,于是父子进程各自持有独立的一份。
    • fork子进程需要先拷贝进程必要的数据结构
      • 拷贝内存页表 — 即虚拟内存和物理内存的映射索引表
      • 这个拷贝过程会消耗大量的CPU资源,并且拷贝完成之前整个进程是会阻塞的
      • 阻塞时间取决于整个实例的内存大小
        • 实例越大,内存页表也越大,fork阻塞时间就会越久
    • 在完成了拷贝内存页表之后,子进程和父进程指向的是相同的内存地址空间
      • 这个时候虽然产生了子进程,但是并没有申请和父进程相同的内存大小
      • 真正的内存分离是在写发生的时候,这个时候才会真正拷贝内存的数据
  • AOF重写过程中父进程产生写入的过程
    • Fork出的子进程当前状态是指向了和父进程相同的内存地址空间,这个时候子进程就可以执行AOF重写,将内存中的所有数据写入到AOF文件里
    • 但是同时父进程仍然会有流量写入
      • 如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离
      • 父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险
      • 如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后,都可以在Redis log中看到父进程重新申请了多大的内存空间

3.4 AOF重写日志为什么不共享AOF本身的日志?

AOF重写不复用AOF本身的日志,一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可

3.5 如何触发AOF重写?

有两个配置项在控制AOF重写的触发时机:

  1. auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
  2. auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。

AOF文件大小同时超出上面这两个配置项时,会触发AOF重写。

Reference

  1. https://time.geekbang.org/column/article/271754
  2. https://juejin.cn/post/6844903702373859335
  3. Redis设计与实现

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 stone2paul@gmail.com

文章标题:Redis持久化机制-AOF

文章字数:2.9k

本文作者:Leilei Chen

发布时间:2021-07-04, 12:02:48

最后更新:2021-07-04, 12:05:05

原始链接:https://www.llchen60.com/Redis%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6-AOF/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏