Redis的持久化机制—RDB与AOF|Java 开发实战

697 阅读12分钟

Redis之所以那么快的一个重要原因就是Redis是一个高性能的key-value内存数据库,所有的数据都保存在内存中,为了保证在宕机或者重启之后内存数据不丢失,Redis提供了持久化机制,将内存中的数据库状态保存到磁盘,并且在启动的时候可以快速进行恢复。

RDB持久化机制

RDB是Redis提供的一个保存内存数据库状态快照的一个功能,防止内存中的数据丢失。RDB持久化功能生成的RDB文件是一个经过压缩的二进制文件,可以通过这个文件在Redis启动的时候恢复Redis的内存状态到保存RDB文件时。

因为RDB文件中保存的是二进制文件,并且每隔一段时间就保存一次,相对而言,RDB文件的体积和执行速度较快,Redis在重启时可以通过RDB文件快速恢复内存中的数据,不过RDB文件的缺点就是数据可能不全。RDB快照是某个时间点的一次全量数据备份

RDB文件的写入

SAVE或者BGSAVE命令均可以生成RDB文件

  • SAVE创建RDB文件

    SAVE命令会阻塞Redis的服务器进程,直到创建RDB文件为止,在服务器进程阻塞期间,服务器不能处理任何请求

  • BGSAVE创建RDB文件

    BGSAVE命令会fork一个子进程,然后通过子进程来生成RDB文件,Redis主进程可以继续接收客户端请求。

    BGSAVE的主进程阻塞时间只有fork阶段的那一下,相对于save,阻塞时间很短

    • COW机制

      因为在子进程创建RDB文件的过程中,会将文件中的所有数据写到文件,主进程在继续处理客户端请求时,如果对内存中的数据更新,那么子进程和父进程是共享内存数据,就会导致有数据的问题。

      所以,COW(copy on write)机制,指的是修改共享资源时,将共享资源copy一份,加锁后修改,再将原容器的引用指向新的容器,这样每个进程之间操作的内存就不是同一个,不会产生数据问题。

      但是缺点就是,COW会将内存copy一份副本,这时内存中的数据就会有两份一样的内存数据,相当于内存减半,需要有足够的内存才可以实现COW机制。

    • Redis的COW机制

      1. Redis创建子进程以后,根本不进行数据的copy,主进程与子线程是共享数据的。主进程继续对外提供读写服务,子进程执行写入RDB服务

      2. 虽然不copy数据,但是kernel会把主进程中的所有内存页的权限都设为read-only,主进程和子进程访问数据的指针都指向同一内存地址

      3. 主进程发生写操作时,因为权限已经设置为read-only了,所以会触发页异常中断(page-fault)。在中断处理中,需要被写入的内存页会复制一份,复制出来的旧数据交给子进程使用,然后主进程对原有的数据页进行写入

      因为数据都是通过页为单位进行存储的,所以可以再主进程操作数据时,对需要操作的那一页数据进行单独的复制,在保存数据的正确性的前提下,还可以减少内存的浪费

      Redis的COW机制也是经过优化,防止内存的浪费以及防止内存数据较大,无法有足够的内存进行copy。

  • 自动保存策略

    Redis的配置文件中可以配置Redis的RDB文件的自动生成策略:

    save 900 1
    save 300 10
    save 60 10000
    

    说明:

    • 当满足900s内有1次修改时,就会执行BGSAVE

    • 当满足300s内有10次修改时,就会执行BGSAVE

    • 当满足60s内有10000次修改时,就会执行BGSAVE

AOF持久化机制

AOF持久化机制和RDB持久化机制不同,RDB是通过定时保存内存中的快照到磁盘中,而AOF文件中保存的是每一次Redis执行的写命令,通过记录写命令来保存数据库的状态。

redis-AOF持久化

AOF文件写入
  • AOF_BUF缓冲区

    现代操作系统中,当调用write函数将输入写入到文件时,操作系统通常都会将写入的数据暂时保存到一个内存缓冲区中,等到缓冲区的空间被填满或者强制调用函数刷新缓冲区时,操作系统才会将缓冲区中的数据写入到磁盘中,因为每次写入磁盘的操作是较慢的。

    Redis服务器在执行完一条命令之后,会将命令追加到aof_buf的缓冲区的末尾,然后通过配置判断当前是否需要将aof_buf缓冲区中的数据刷新到磁盘(即AOF文件中),可以通过配置来保证是每次执行命令都需要将该命令刷新到磁盘还是可以多次执行之后一起刷新到磁盘,可以在数据的安全性和高效率之间在不同的场景选择合适的方案。

  • AOF持久化机制

    aof_buf的刷新行为是通过配置appendfsync控制,一共有3中选择可以进行配置

    • always

      服务器在每次执行完一条命令之后,都需要将aof_buf缓冲区中的数据刷新到AOF文件中,所以always的效率是最慢的一个,不过安全性是最高的,最多只会丢失一个命令数据

    • everysec

      服务器在每隔一秒就将aof_buf缓冲区中数据刷新到AOF文件,即将文件缓冲区的数据也同步到文件中

    • no

      在每次执行写命令时,不会主动将aof_buf缓冲区中的数据刷新到AOF文件中,什么时候刷新是操作系统决定。

    其中always是最安全的一种策略,不过频繁刷新到磁盘会影响redis的性能,everysec是一种相对安全以及保证效率的策略,最多丢失1s的数据,大部分的场景应该都是可以接收,no在不要求数据的可靠性的情况下可以使用。

AOF文件的加载和还原

在AOF文件中,保存的都是一条条的写命令,所以再重新还原Redis的数据库状态时,只需要重新执行AOF文件中的所有命令即可。

不过Redis在执行AOF文件中的命令之前,会先创建一个伪客户端,因为Redis的命令只能在客户端上下文中执行,每次读取一条命令循环执行,直到执行完毕。

Redis在重新启动恢复内存数据时,会优先选择使用AOF文件来恢复数据,因为AOF文件的数据相对较全。不过AOF文件中记录的是所有的写命令,那么时间较长的话,AOF文件的体积是非常大的,在重启恢复数据库状态的时候,就会比较慢,导致Redis不能快速提供服务。

AOF重写机制

因为AOF文件体积较大的问题,影响到了Redis服务器的数据库状态的恢复以及磁盘文件的存放,Redis提供了重写机制来解决AOF文件较大的问题。

AOF重写在Redis中的实现和RDB有些类似,因为重写需要大量的操作,可能会阻塞主进程处理请求,所以采用了后台重写的方式。

简单讲,AOF重写就是将当前数据库中的键值重新写入到一个新的AOF文件中,然后覆盖之前的AOF文件。

  • AOF重写步骤

    • 创建子进程进行AOF的重写,主进行继续处理命令请求

    • 子进程从数据库中读取每个键现在的值,然后将多个命令合并为一个命令,使用一条命令完成之前多条命令的操作,并且由部分数据已经过期或者已经删除,AOF文件中已经不需要记录这部分数据,同样的,命令也不需要再记录到AOF文件中。

      例如:多条SADD命令可以合并为一条SADD key1 value1 key2 value2 ...命令,AOF文件中只需要记录一条命令

    • 在AOF重写过程中,如果有需要对当前的数据库数据进行变更的命令,那么Redis就会通过一个AOF重写缓冲区来记录当前的操作指令

      所以在AOF重写期间,如果客户端执行一条写命令,那么首先会执行这个命令,然后将这条命令写入到AOF缓冲区,主要是为了将命令同步到现有的AOF文件,然后将命令写入到AOF重写缓存区,向新的AOF文件同步数据

    • 子进程完成AOF重写之后,会通知主进程重写完成,主进程接收到完成的信号,会将AOF重写缓冲区的数据写入到新的AOF文件,这个时候新的AOF文件和数据库的状态保持一致。

    • 对新的AOF文件重名名,原子的覆盖当前的AOF文件,完成新旧AOF文件的替换,后续的命令同步会写入到新的AOF文件

    执行AOF重写的命令为:BGREWRITEAOF

  • Redis 4.0的AOF持久化机制

    AOF不再记录全部的写命令,而是增量的写命令,当RDB开始持久化时,这个时候AOF会开始增量的记录当前写操作的写命令,当RDB持久化完成之后,就不会再记录写命令。只记录RDB持久化开始到持久化结束的这一段时间的增量日志,这部分日志相对会较小。

    在Redis恢复的时候,也可以快速的恢复内存中的数据,保证效率和数据的安全性。

Redis4.0的混合持久化机制

Redis 3.x中,持久化机制是只能选择AOF或者RDB中的一种,Redis 4.0在RDB持久化和AOF持久化中并没有做选择,而是全都要

开启混合持久化

4.0版本的混合持久化默认关闭的,通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,默认是禁用的,可通过config set修改。

混合持久化过程

混合持久化同样也是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据,如下图:

混合持久化过程

两种持久化机制对比

img

可以看到两种持久化机制各有优缺点:

  • 仅使用RDB快照方式恢复数据,由于快照时间粒度较大,时回丢失大量数据。
  • 仅使用AOF重放方式恢复数据,日志性能相对 rdb 来说要慢。在 Redis 实例很大的情况下,启动需要花费很长的时间。
混合持久化机制

将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是自持久化开始到持久化结束的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小。相当于:

  • 大量数据使用粗粒度(时间上)的rdb快照方式,性能高,恢复时间快。
  • 增量数据使用细粒度(时间上)的AOF日志方式,尽量保证数据的不丢失。

在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,重启效率因此大幅得到提升。

当然,解决了部分问题,同时Redis4.0的混合持久化也有对应的缺点:兼容性较差,aof文件中是混合存储RDB和AOF格式,在低版本无法识别这种日志格式的文件

小结

  • AOF持久化机制通过保存写命令来记录数据库的状态
  • RDB持久化机制通过定期保存数据库的快照
  • AOF持久化的策略有不同的选择,默认是每秒保存一次,当保存写命令的时候不是直接保存到AOF文件,而是先保存到AOF缓冲区中,然后再将AOF缓冲区中的数据刷到磁盘文件(AOF文件)
  • Redis提供了AOF重写机制来解决AOF文件较大的问题,在AOF重写时,会有AOF重写缓冲区来保证重写期间的数据一致性
  • AOF重写的实质就是读取当前数据库中键值对来实现命令的合并
  • Redis 4.0的混合持久化机制,更好的保证了Redis恢复内存数据库的效率,在数据安全性和加载的效率性保证了平衡性。

微信公众号指尖上的代码,欢迎关注~ 一起学习 一起进步

原创不易, 点个赞再走呗~ 欢迎关注,给你带来更精彩的文章!

你的点赞关注是写文章最大的动力~