Redis持久化:RDB和AOF

Redis提供的持久化机制

Redis是一个键值对数据库服务器,服务器中通常包含着任意个非空数据库,而每个非空数据库又可以包含任意个键值对
,为了方便起见,我们将服务器的非空数据库以及它们的键值对统称为数据库状态。

Redis 是内存服务器,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器的数据库状态也会消失不见

为了解决这个问题,Redis提供了RDB和AOF持久化,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。

RDB持久化

在 Redis 运行时, RDB 程序将当前内存中的数据库快照保存到磁盘文件中, 在 Redis 重启动时, RDB 程序可以通过载入 RDB 文件来还原数据库的状态。

rdb2.png
rdb3.png

RDB 文件的创建与载入

RDB的触发机制主要有三种方式:SAVE(同步)、BGSAVE(异步、自动

SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,服务器进程阻塞起见,服务器不能处理任何请求:

1
2
redis> SAVE
OK

和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求:

1
2
redis> BGSAVE
Background saveing started

BGSAVE主要使用的是fork系统调用创建子进程,父进程继续处理命令,子进程进行RDB持久化

前两种是属于手动触发Redis持久化,而Redis还有一种自动触发机制
最常见的情况在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。例如默认配置文件中有以下三行:

1
2
3
save 900 1
save 300 10
save 60 10000

只要上面三行任意一条满足时,就会执行bgsave。
另外在Redis主从复制,如果从节点执行全量赋值,主节点会执行BGSAVE命令,从rdb文件发送给从结点,并且执行shutdown命令时,也会自动执行rdb持久化。

关于RDB文件的载入,和使用SAVE命令或者BGSAVE命令创建RDB文件不同,RDB文件的载入是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件的存在,它就会自动载入RDB文件,另外值得一提的是,因为AOF的更新频率通常比RDB文件的更新频率高,所以:

  • 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。
  • 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。服务器判断该用哪个文件来还原数据库状态如图

rdb4.png

BGSAVE 命令执行时的服务器状态

因为BGSAVE命令的保存工作是由子进程来执行的,所以子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端命令请求,但是在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令会和平时有所不同。

首先,在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE也会产生两个竞争条件。

其次在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行时为了避免父进程(服务器进程)和子进程同时执行两个rdbSave(RDB底层调用)调用,防止产生竞争调用。
最后,BGREWRITEAOF和BGSAVE两个命令不能同时执行:

  • 如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。
  • 如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。

因为BGREWRITEAOF和BGSAVE两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑——并发出两个子进程,并且这两个子进程都同时执行大量的磁盘操作会有很大的开销

RDB文件载入时的服务器状态

服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止

RDB有什么问题?

  • 耗时、耗性能;主要体现是RDB是将所有redis数据存入rdb文件中,如果rdb很大,就会有很大的硬盘消耗,时间是O(n),并且在BGSAVE生成子进程,消耗内存。
  • 不可控,丢失数据,详情见下表
时间戳 save
T1 执行多个写命令
T2 满足RDB自动创建的条件
T3 再次执行多个写命令
T4 宕机

通过表可以知道,如果在T4出现宕机,此时会丢失数据

AOF持久化

与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF是通过保存Redis服务器所执行的写命令来记录数据库状态的。

aof.png

AOF的三种策略

为了提高文件写入效率,在现代操作系统中,当用户将数据写入文件时(write命令),操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但如果系统崩溃,内存缓冲区中的数据将会丢失。因此可以设置同步选项,强制操作系统什么时候将缓冲区中的数据写入到硬盘中(fsync命令),Redis提供了以下三种同步策略:

  • always:每个写命令都同步
  • everysec(默认):每秒同步一次
  • no:让操作系统来决定何时同步

always会严重降低服务器的性能,而no的不可控性太强,因此Redis使用everysec作为默认配置,但在系统崩溃时可能会丢失一秒的数据。

AOF文件的载入与还原

因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态。

aof2.png

AOF重写

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的话,体积过大的AOF文件很可能对redis服务器、甚至整个计算机造成影响,并且如果文件体积越大,使用AOF文件来执行数据还原所需的时间就更多。

文件重写主要是针对以下一些语句:

  • 过期的数据(如expire),可以不用再写入文件。
  • 多次INCR命令可以合并为一个SET命令。
  • 无效的命令不再写入文件,比如有些数据被删除了。

手动触发

可以直接调用bgrewriteaof命令重写文件,该命令的执行与bgsave有些类似,都是fork子进程进行具体的工作,且都只有在fork时阻塞。

自动触发

默认配置是当AOF文件大小是上次重写后大小的一倍(auto-aof-rewrite-min-size)且文件大于64M时触发(auto-aof-rewrite-percentage)。

具体流程

  1. 执行BGREWRITEAOF命令
  2. 父进程执行fork操作创建子进程。
  3. 子进程创建后,Redis的所有写命令依然写入AOF缓冲区,并根据设置策略同步到硬盘,保证原有AOF机制的正确。
  4. 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此 Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。
  5. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。
  6. 子进程写完新的AOF文件后,向父进程发信号,父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
  7. 使用新的AOF文件替换老文件,完成AOF重写。

aof3.png

RDB和AOF

命令 RDB AOF
启动优先级
体积
恢复数据
数据安全性 丢数据 根据策略决定
轻重

参考资料

  • Redis设计与实现 黄健宏 机械工业出版社
  • Redis开发与运维 付磊 / 张益军 机械工业出版社