Redis持久化

0.前言

持久化是指将数据写入持久存储,例如固态磁盘 (SSD)。Redis支持以下几种持久化方式:

  • RDB(Redis DataBase):以指定的时间间隔执行数据集的时间点快照
  • AOF(Append Only File):记录server收到的每个写操作命令,在重新启动时,再次执行这些命令来重建数据
  • RDB+AOF:重启后,AOF用于重建数据

为何要持久化?

  • 数据放在内存,容量有限、断电丢失

1.RDB

Redis将内存数据快照写入rdb后缀的二进制文件中,这叫snapshot。

触发方式

redis RDB持久化有两种触发方式:

  • 手动触发:使用SAVEBGSAVE命令
  • 自动触发:比如每隔N秒,如果数据有M个改动

手动触发

  • save:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存 比较大的实例会造成长时间阻塞,线上环境不建议使用
  • bgsave:fork创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短

自动触发

以下四种情况会自动触发:

  • redis.conf中设置了save m n,即每隔m秒检查数据是否有n次改动
  • 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点
  • 执行debug reload命令重新加载redis时也会触发bgsave操作
  • 默认情况下执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作

优缺点

优点:

  • RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景
  • RDB加载RDB文件恢复数据比AOF快

缺点:

  • 实时性不够,无法做到秒级的持久化
  • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高
  • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全

2.AOF

Redis是“写后”日志,Redis先执行命令,把数据写入内存,然后才记录日志。日志里记录的是Redis收到的每一条命令,这些命令是以文本形式保存。
PS: 大多数的数据库采用的是写前日志(WAL),例如MySQL,通过写前日志和两阶段提交,实现数据和逻辑的一致性。

写后日志

优点:

  • 避免额外检查开销:Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错
  • 不会阻塞当前写操作

缺点:

  • 数据丢失
  • 主线程写磁盘压力大

AOF的实现

AOF实现分为三个阶段:命令追加、文件写入、文件同步

命令追加

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

struct redisServer {
    // ...
    // AOF
缓冲区
    sds aof_buf;
    // ...
};

例如:
执行如下操作:

redis> SET KEY VALUE
OK

那么服务器在执行这个SET命令之后,会将以下协议内容追加到aof_buf缓冲区的末尾:

*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n

文件写入和同步

因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。
关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了三种写回策略:
不同appendfsync值产生不同的持久化行为

  • Always:每个写命令执行完,立马同步地将日志写回磁盘
  • Everysec:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘
  • No:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘

为了提高文件的写入效率,在现代操作系统中,当用户调用write函数,将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内存缓冲区里面,等到缓冲区的空间被填满、或者超过了指定的时限之后,才真正地将缓冲区中的数据写入到磁盘里面。
这种做法虽然提高了效率,但也为写入数据带来了安全问题,因为如果计算机发生停机,那么保存在内存缓冲区里面的写入数据将会丢失。
为此,系统提供了fsync和fdatasync两个同步函数,它们可以强制让操作系统立即将缓冲区中的数据写入到硬盘里面,从而确保写入数据的安全性

AOF文件载入和数据还原

载入:

AOF重写

随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF文件很可能对Redis服务器、甚至整个宿主计算机造成影响,并且AOF文件的体积越大,使用AOF文件来进行数据还原所需的时间就越多。
为了解决AOF文件体积膨胀的问题,Redis提供了**AOF文件重写(rewrite)**功能。
通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。
AOF重写通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作
举个例子:

执行过程

  • 主线程fork出子进程重写aof日志
  • 子进程重写日志完成后,主线程追加aof日志缓冲
  • 替换日志文件

AOF重写过程
在执行BGREWRITEAOF命令时,Redis服务器会维护一个AOF重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作

几个问题

AOF重写会阻塞吗?

AOF重写过程是由后台进程bgrewriteaof来完成的。主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。
所以aof在重写时,在fork进程时是会阻塞住主线程的。

AOF日志何时会重写?

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

  • auto-aof-rewrite-min-size:表示运行AOF重写时文件的最小大小,默认为64MB。
  • auto-aof-rewrite-percentage:这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值
为何不复用旧的AOF文件?
  • 父子进程同时写同一个文件,涉及资源竞争问题
  • 如果AOF重写失败,则会导致原AOF文件内容丢失

优缺点

优点:

  • 灵活配置,提供三种文件写入和同步策略
  • AOF文件是只读的,不存在断电文件指针丢失问题
  • 当AOF文件过大时,支持后台自动重写AOF文件,该过程不会导致数据丢失
  • AOF文件保存了Redis写命令,可读性强

缺点:

  • 相同情况下,AOF文件大小要比RDB文件大
  • AOF执行过程要比RDB慢,取决于不同的fsync策略

3.从持久化数据中恢复数据

数据的备份、持久化做完了,我们如何从这些持久化文件中恢复数据呢?如果一台服务器上有既有RDB文件,又有AOF文件,该加载谁呢?

  • 优先加载AOF
  • 其次才是RDB

参考文章


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!