RDB与AOF持久化底层原理

2024-10-09 15:12:33 浏览数 (5)

Redis作为一个键值对数据库服务器,它保存的数据需要存储到内存中以维护数据的持久性,而实现持久化策略主要由RDB与AOF两种,本文旨在介绍RDB与AOF的底层持久化原理

RDB与AOF命令介绍

RDB命令与AOF命令都可以对数据库数据进行持久化,略有不同的是,RDB文件保存的是数据库的最终数据,并且一般来说计算机内部保存的都是经过压缩的二进制RDB文件,而AOF文件保存的时数据库的执行语句,可以说,RDB持久化关注的是最后的数据库状态,而AOF持久化关注的是服务器执行的具体命令

RDB持久化与AOF持久化RDB持久化与AOF持久化

RDB持久化

RDB文件的创建与载入

有两个Redis命令可以生成RDB文件,分别为SAVEBGSAVE,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重写等,这些后续笔者都会写文章讲解的,希望对读到这儿的你有所收获!!!

0 人点赞