Redis作为一个键值对数据库服务器,它保存的数据需要存储到内存中以维护数据的持久性,而实现持久化策略主要由RDB与AOF两种,本文旨在介绍RDB与AOF的底层持久化原理
RDB与AOF命令介绍
RDB命令与AOF命令都可以对数据库数据进行持久化,略有不同的是,RDB文件保存的是数据库的最终数据,并且一般来说计算机内部保存的都是经过压缩的二进制RDB文件,而AOF文件保存的时数据库的执行语句,可以说,RDB持久化关注的是最后的数据库状态,而AOF持久化关注的是服务器执行的具体命令:
RDB持久化
RDB文件的创建与载入
有两个Redis命令可以生成RDB文件,分别为SAVE与BGSAVE,SAVE命令会阻塞Redis服务器进程,知道RDB文件创建完毕,而BGSAVE在执行过程中会派生出一个子进程,由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
值得一提的是,在Redis服务器启动时,服务器会自动加载当前目录中的RDB文件,而不需要用户手动载入RDB文件,而AOF文件由于更新频率比RDB高(由于执行语句一直在运行),那么如果服务器开启了AOF持久化功能,服务器会优先使用AOF文件来还原数据库状态,具体执行顺序如下图:
BGSAVE命令执行时的服务器状态
同时,在BGSAVE命令执行过程中,SAVE,BGSAVE,BGREWRITEAOF三个命令的执行方式与一般情况也有所不同:
SAVE:在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令与BGSAVE命令同时执行是为了避免父进程与子进程同时进行两个rdbSave调用,防止产生竞争条件
BGSAVE:与SAVE命令同理,如果发送了BGSAVE命令请求也会被拒绝,防止产生竞争
BGREWRITEAOF:这里需要分两方面进行考虑
(1)如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行
(2)如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝,这里可能会有困惑,因为BGREWRITEAOF与BGSAVE两个命令其实都由子进程执行,它们在操作方面其实并没有什么冲突,但是出于性能方面考虑,如果这两个子进程在短时间内都执行了大量的磁盘写入操作,确实会很耗费性能
BGSAVE的自动间隔性保存
在使用BGSAVE命令持久化文件时,由于该命令会在后台创建一个子进程持久化数据,因此Redis允许用户通过配置服务器的save选项,让服务器在满足条件时自动执行BGSAVE命令,举个例子,如果给save的配置如下:
代码语言:txt复制save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任何一个,BGSAVE命令就会被自动执行:
(1)服务器在900秒之内,对数据库进行了至少1次修改
(2)服务器在300秒之内,对数据库进行了至少10次修改
(3)服务器在60秒之内,对数据库进行了至少10000次修改
接下来我会介绍Redis如何通过这个配置自动执行BGSAVE命令
BGSAVE命令自动执行的底层原理
我们首先介绍一下Redis底层的redisServer结构体:
代码语言:c复制struct redisServer {
// ...
//记录了保存条件的数组
struct saveparam *saveparams;
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
//...
};
每个字段含义为:
(1)saveparams:数组,每个元素时saveparam结构,每个结构都保存了一个save选项设置的保存条件,具体结构为:
代码语言:c复制struct saveparam {
// 秒数
time_t seconds;
// 修改数
int changes;
};
那么假设save配置为:save 900 1,那么对应的seconds就为900,changes就为1,字段一一对应
(2)dirty计数器:记录距离上一次成功执行BGSAVE或SAVE命令后,服务器对服务器中的所有数据库进行了多少次修改(包括写入、删除、更新等操作),比如执行SADD database Redis MongoDB MariaDB
命令,那么dirty会增加3
(3)lastsave属性:一个UNIX时间戳,记录了服务器上一次成功执行SAVE或BGSAVE命令的时间
当然了,还需要有判断函数进行周期性判断条件是否满足,只有条件满足时才会进行自动持久化,周期性判断伪代码如下:
代码语言:c复制def serverCron():
# ...
# 遍历所有保存条件
for saveparam in server.saveparams:
# 计算距离上次执行保存操作有多少秒
save_interval = unixtime_now() - server.lastsave
# 如果数据库状态的修改次数超过条件所设置的次数
# 并且距离上次保存时间超过条件所设置的时间
# 那么执行保存操作
if server.dirty >= saveparam.changes and
save_interval >= saveparam.second:
BGSAVE()
# ...
只要有任意一个条件满足,那么服务器就会执行BGSAVE命令,执行命令后,将saveparam的dirty字段清空,将save_interval设置为当前时间
以上便是RDB命令持久化的底层原理
AOF持久化
AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤
命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令后,会以写一个是将被执行的写命令追加到服务器状态的aof_buf缓冲区的结尾:
代码语言:c复制struct redisServer {
// ...
//AOF缓冲区
sds aof_buf;
// ...
};
举例来讲,如果服务器执行命令:SET KEY VALUE
,那么服务器执行完这个SET命令后,会将以下协议追加到aof_buf中:
3rn$3rnSETrn$3rnKEYrn$5rnVALUErn
AOF文件的写入与同步
因为服务器执行命令后会将协议存储到aof_buf中,并没有持久化到磁盘中,所以还需要服务器进行进一步的写入操作,具体写入操作主要由flushAppendOnlyFile函数执行,Redis的服务器进程实际上就是一个事件循环,因此在每次结束一个事件循环之前,服务器都会调用这个函数判断是否要将aof_buf缓冲区的内容写入保存到AOF文件里面,这个过程主要由一下伪代码表示:
代码语言:c复制def eventLoop():
while True:
# 处理文件事件,接收命令请求以及发送命令回复
# 处理命令请求时可能会有新内容被追加到aof_buf缓冲区中
processFileEvents()
# 处理时间事件
processTimeEvents()
# 考虑是否要将aof_buf中的内容写入和保存到AOF文件中
flushAppendOnlyFile()
其中flushAppendOnlyFile函数的行为主要由服务器配置的appendfsync选项的值来决定,各个不同值产生行为如下表:
appendfsync选项的值 | flushAppendOnlyFile函数的行为 |
---|---|
always | 将aof_buf缓冲区的所有内容写入并同步到AOF文件,效率最低,但安全性最高 |
everysec | 将aof_buf缓冲区中的所有内容写入到AOF文件,如果上次同步AOF文件的时间距离现在超过一秒钟,那么再次对AOF文件进行同步,这个同步操作由一个线程专门负责,效率比较高,安全性良好 |
no | 将aof_buf缓冲区的所有内容写入到AOF文件,但是并不对AOF文件进行同步,何时同步由操作系统(用户)决定,效率最高,但安全性差 |
appendfsync的值默认为everysync,保证了AOF文件可以及时同步,提高了安全性
至此RDB与AOF的持久化底层原理就都介绍完毕了,有关于RDB与AOF还有很多值得探讨的点,比如RDB文件结构,AOF重写等,这些后续笔者都会写文章讲解的,希望对读到这儿的你有所收获!!!