【Redis 持久化】AOF和RDB

Redis数据持久化

Redis提供了四种持久化策略:RDB (Redis Database)、AOF、RDB + AOF 和 不持久化。

  • RDB(Redis Database)

    在指定的时间间隔内将内存中的数据集以快照形式写入磁盘。

    • 优点:

      • RDB 快照是一个压缩过的非常紧凑的文件,保存着某个时间点的数据集,适合做数据的备份,灾难恢复;

      • 可以最大化Redis的性能,在保存RDB文件,服务器进程只需fork一个子进程来完成RDB文件的创建,父进程不需要做IO操作;

      • 与AOF相比,恢复大数据集的时候会更快。

    • 缺点:

      • RDB 的数据安全性不如 AOF,生成两次快照之间的发生故障,存在数据丢失风险。

      • RDB 需要经常 fork() 以便使用子进程持久保存在磁盘上。如果数据集很大,fork() 可能会很耗时,可能会导致 Redis 停止为客户端提供几毫秒甚至一秒的服务。

  • AOF(Append Only File)

    AOF 持久化记录服务器收到的每一个写操作。然后可以在服务器启动时再次重放这些操作,重建原始数据集。使用与 Redis 协议本身相同的格式记录命令。

    • 优点:

      • 数据更完整,安全性更高,秒级数据丢失(取决fsync策略,如果是everysec,最多丢失1秒的数据);
      • AOF文件是一个只进行追加的日志文件,且写入操作是以Redis协议的格式保存的,内容是可读的,适合误删紧急恢复。
    • 缺点:

      • 对于相同的数据集,AOF文件的体积要大于RDB文件,数据恢复也会比较慢;
      • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。 不过在一般情况下, 每秒 fsync 的性能依然非常高。
  • RDB + AOF

    同时使用AOF 和 RDB 两种持久化组合。

  • 不持久性

    一般作为缓存的时候会用到。

RDB(Redis Database)

RDB 持久化是以指定的时间间隔执行,将内存中数据集的时间点快照写入磁盘。

在创建快照之后,用户可以备份该快照,可以将快照复制到其他服务器以创建相同数据的服务器副本,或者在重启服务器后恢复数据。

RDB 文件是一个压缩过的文件,因此RDB持久化的体积比AOF小,因为存储的是数据,所以恢复很快,性能好,但是可能会丢失最后一次持久化的数据。RDB是Redis默认的持久化方式。

触发快照的时机

触发快照的时机:

  • 执行 save 和 bgsave 命令;

    • save命令:在主进程生成 RDB 文件,由于和执行操作命令在同一个线程,所以,如果写入 RDB 文件的时间太长,会阻塞主线程。

    • bgsave命令:fork()一个子进程,由子进程负责创建RDB文件,Redis主进程继续处理操作命令,这样可以避免主线程的阻塞。

  • 配置文件的选项设置 save <seconds> <changes> 规则,自动间隔性执行 bgsave 命令;

    例如,60秒内,对数据库进行了至少10000次修改:

    save 60 10000
    

    注意,执行快照是一个比较重的操作,如果频率太频繁,可能会对 Redis 性能产生影响。

  • 主从复制时,从库全量复制同步主库数据,主库会执行bgsave;

  • 执行flushall命令清空服务器数据;

  • 执行shutdown命令关闭Redis时,会执行save命令;

AOF(Append Only File)

Redis 首先执行写操作命令,然后,再将该命令记录到 AOF 日志里,这样的优缺点如下:

  • 优点

    • 避免额外的检查开销

      如果当前执行的命令语法有问题,如果没有对其进行语法检查,这条错误的命令记录到 AOF 日志里后,在 Redis 在使用日志恢复数据时,就可能会出错。

    • 不会阻塞当前写操作命令的执行

  • 缺点

    • 存在数据丢失的风险

      当 Redis 在还没来得及将命令写入到磁盘时,如果服务器宕机,此时,数据就会有丢失的风险。

    • 存在阻塞后续命令执行的风险

      由于写操作命令执行成功后才记录到 AOF 日志,所以,不会阻塞当前写操作命令的执行,但是,可能会阻塞后续命令的执行。
      执行命令将命令写入日志两个步骤,在主进程里面是串行执行的。

AOF 日志写入过程

Linux允许把脏缓冲区写入块设备的操作延迟执行(write back),这样可以显著提高系统的性能。

Redis 写入 AOF 日志的过程,如下图:

image

可以看到,Redis 每执行一个写命令,都会把该命令以协议格式先追加到 server.aof_buf 缓存区的末尾,而不是直接写入文件,避免每次有命令都直接写入硬盘,减少磁盘 I/O 操作次数

AOF 记录 Redis 的每个写命令的步骤为:命令追加(append)、文件写入(write)和文件同步(sync),具体步骤如下:

  • Redis 执行完写操作命令后,会将命令追加(append)到 server.aof_buf 缓冲区;

  • 执行通过 write() 系统调用,将 server.aof_buf 缓冲区的数据写入到 AOF 文件,此时,数据还在内核 page cache 缓冲区,没有写入到磁盘,等待内核将数据写入硬盘;

  • 操作系统将 page cache 缓冲区中的内容,刷新入 AOF文件。

对于何时把 server.aof_buf 缓冲区的内容写入保存在 AOF 文件中,Redis提供了三种策略:

  • appendfsync always:每次写操作命令执行完后,同步执行 fsync() 系统调用,将 AOF 日志数据写回硬盘;

  • appendfsync everysec:每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后,通过异步线程每隔一秒执行 fsync() 系统调用,将缓冲区里的内容写回到磁盘;

  • appendfsync no:每次写操作命令执行完后,只把命令写入到 AOF 文件的内核缓冲区,操作系统控制写回的时机。

三种数据刷盘策略的优缺点:

策略 写回时机 优点 缺点
always 同步写回 可靠性高、最大程度保证数据不丢失 性能最差,每个命令都会写回磁盘
everysec 每秒写回 性能适中 宕机时,会有1秒内数据丢失风险
no 由操作系统控制 性能最好 宕机时,会有大量数据丢失的风险

AOF 重写机制

随着服务器运行时间的流逝,可能会频繁的对同一个 key 进行很多写操作,AOF 文件中的内容会越来越多,文件越来越大,如果不加以控制,会对服务器的性能造成影响,还原数据的时间也加长。

Redis 支持一个有趣的特性:它能够在后台重建 AOF,而不会中断对客户端的服务。每当我们手动执行 BGREWRITEAOF 命令时,Redis 都会重新记录在内存中重建当前数据集所需的最短命令序列

AOF 重写

image

执行 bgrewriteaof 命令,重写的流程如下:

  • Redis 主进程会创建(fork)一个“重写”子进程(bgrewriteaof),这个子进程会首先读取现有的 AOF 文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。

  • 在重写期间,Redis 主进程继续处理命令请求,如果有写入的命令,会追加到 AOF 缓冲区(aof_buf) ,同时还会追加到 AOF 重写缓冲区(aof_rewrite_buf )

  • 当子进程完成重写工作后,它会给父进程发一个信号,父进程会把 AOF 重写缓冲区的内容写进新的 AOF 临时文件中。

  • Redis 就会用新 AOF 文件来代替旧 AOF 文件,这样可以保证新的AOF文件与当前数据库数据的一致性。

通过 AOF 重写机制,Redis 会创建一个新的 AOF 文件替代现有的 AOF 文件,新旧两个文件保存的数据库状态相同,但是,新的 AOF 文件不会包含浪费空间的冗余命令,只保存必须的命令,因此新的 AOF 体积就要小得多。

这样,即使某个键值对被多条写命令反复修改,最终,也只需要用一条命令去记录这个键值对当前的最新状态,以代替之前记录这个键值对的多次修改记录,这样就减少了 AOF 文件中的命令数量。

同时,为了避免重写过程对现有的 AOF 文件会造成污染,所以,需要先创建一个新的 AOF 文件,待重写完成后,再将新的 AOF 文件替换现有的 AOF 文件。

主进程在通过 fork 系统调用生成 bgrewriteaof 子进程时,操作系统会把主进程的内存页复制一份给子进程,这个内存页记录着虚拟地址和物理地址映射关系,而不会复制物理内存,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。

image

可以看到,当操作系统复制父进程资源的时候,父进程是处于阻塞中的状态,但是,由于内存页的大小相比实际的物理内存小很多,所以,通常复制内存页的过程是比较快的。但是,如果父进程的内存数据非常大,那内存页也会很大,此时,父进程在通过 fork 创建子进程的时候,阻塞的时间也会越久。

这样,子进程就共享了父进程的物理内存数据了,能够节约物理内存资源,页表对应的页表项的属性会标记该物理内存的权限为只读。

当父进程或者子进程在向这个内存发起写操作时,CPU 就会触发写保护中断,这个写保护中断是由于违反权限导致的,然后操作系统会在写保护中断处理函数里进行物理内存的复制,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写,最后才会对内存进行写操作,这个过程被称为写时复制(Copy On Write)

写时复制

只有在发生写操作的时候,操作系统才会去复制物理内存,这样是为了防止 fork 创建子进程时,由于物理内存数据的复制时间过长而导致父进程长时间阻塞的问题。

image

所以,有两个阶段会导致阻塞父进程:

  • 创建子进程的过程中,由于要复制父进程的内存页,阻塞的时间跟页表的大小有关,页表越大,阻塞的时间也越长;
  • 创建完子进程后,如果子进程或者父进程修改了共享数据,就会发生写时复制,这期间会拷贝物理内存,如果内存越大,自然阻塞的时间也越长。

AOF 重写缓冲区

在重写 AOF 期间,当 Redis 执行完一个写命令之后,它会同时将这个写命令写入到 AOF 缓冲区AOF 重写缓冲区,如下图所示:

image

在 bgrewriteaof 子进程执行 AOF 重写期间,主进程需要执行以下三个步骤:

  • 执行客户端发来的命令;

  • 将执行后的写命令追加到 AOF 缓冲区

  • 将执行后的写命令追加到 AOF 重写缓冲区

这样,就避免了重写 AOF 日志过程中,主进程修改了已经存在 key-value,导致这个 key-value 数据在子进程的内存数据跟主进程的内存数据不一致。

当子进程完成 AOF 重写工作(扫描数据库中所有数据,逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志)后,会向主进程发送一条信号,信号是进程间通讯的一种方式,且是异步的。

子进程重写的优点

通过后台线程执行重写操作,有两个好处:

  • 子进程进行 AOF 重写期间,主进程可以继续处理命令请求,从而避免阻塞主进程。

  • 子进程带有主进程的数据副本,不会导致线程安全问题。

    如果是使用线程,多线程之间会共享内存,那么,在修改共享内存数据的时候,需要通过加锁来保证数据的安全,而这样就会降低性能。

    而创建子进程时,父子进程是共享内存数据的,不过这个共享的内存只能以只读的方式,而当父子进程任意一方修改了该共享内存,就会发生写时复制(Copy On Write),于是父子进程就有了独立的数据副本,就不用加锁来保证数据安全。

总结

触发重写机制后,主进程就会创建重写 AOF 的子进程,此时父子进程共享物理内存,重写子进程只会对这个内存进行只读,重写 AOF 子进程会读取数据库里的所有数据,并逐一把内存数据的键值对转换成一条命令,再将命令记录到重写日志(新的 AOF 文件)。

子进程重写过程中,主进程依然可以正常处理命令,如果此时主进程修改了已经存在 key-value,就会发生写时复制,注意这里只会复制主进程修改的物理内存数据,没修改物理内存还是与子进程共享的。

RDB + AOF 组合

Redis 4.0 开始支持将 RDB 和 AOF 组合使用,该方法叫混合使用 AOF 日志和内存快照,也叫混合持久化。通过如下配置项设置:

aof-use-rdb-preamble yes

当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

image

也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。

加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。

参考:

posted @ 2023-04-06 21:34  LARRY1024  阅读(139)  评论(0编辑  收藏  举报