欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RDB 持久化

程序员文章站 2022-05-28 12:14:17
...
        RDB 持久化功能可以将 Redis 在某个时刻内存中的数据库状态保存到 RDB 磁盘文件里面,以免数据丢失。RDB 文件是一个经过压缩的二进制文件,通过该文件亦可还原生成 RDB 文件时的数据库状态。
        RDB 持久化可以手动执行,也可根据服务器配置选项定期执行。
        SAVE 和 BGSAVE 命令可以用于生成 RDB 文件。SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕,在阻塞期间,服务器会拒绝客户端的任何命令请求。而 BGSAVE 命令则会单独派生一个子进程来负责创建 RDB 文件,因此服务器进程可以继续处理命令请求。创建 RDB 文件的工作实际上是由底层的 rdbSave 函数来完成的,SAVE 和 BGSAVE 命令只是以不同的方式调用这个函数而已,这可以通过以下的伪代码表示。
def SAVE():
    rdbSave()                # 创建 RDB 文件

def BGSAVE():
    pid = fork()             # 创建子进程
    if pid == 0:
        rdbSave()            # 子进程负责创建 RDB 文件
        signal_parent()      # 完成后向父进程发送信号
    elif pid > 0:
        # 父进程继续处理命令请求,并通过轮询等待子进程的信号
        handle_request_and_wait_signal()
    else:
        handle_fork_error()    # 处理出错情况

        虽然 Redis 在 BGSAVE 命令执行期间仍然可以继续处理客户端的命令请求,但是 SAVE、BGSAVE 和 BGREWRITEAOF 三个命令的处理方式却和平时有所不同。在 BGSAVE 命令执行期间,客户端发送的 SAVE 和 BGSAVE 命令都会被服务器拒绝,这主要是为了避免多个进程同时执行 rdbSave 函数,从而产生竞争条件。而 BGREWRITEAOF 命令在 BGSAVE 命令执行期间会被延迟,直到 BGSAVE 命令执行完毕后才会执行。反过来,如果 BGREWRITEAOF 命令正在执行,那么客户端发送的 BGSAVE 命令则会被服务器拒绝。尽管 BGSAVE 和 BGREWRITEAOF 命令的实际工作都由子进程执行,而且它们在操作方面也没有什么冲突的地方,但之所以不能同时执行它们,主要是出于性能方面的考虑,因为它们都涉及同时执行大量的磁盘写入操作。
        由于 RDB 文件的载入工作是在服务器启动时自动执行的,所以 Redis 没有专门用于载入 RDB 文件的命令,只要服务器启动时检测到 RDB 文件存在,它就会自动载入 RDB 文件。另外值得一提的是,因为 AOF 文件的更新频率通常高于 RDB 文件,因此如果服务器开启了 AOF 持久化功能,那么服务器会优先使用 AOF 文件来还原数据库状态。
        因为 BGSAVE 命令的执行不阻塞服务器,所以 Redis 允许用户通过配置 save 选项设置多个保存条件来让服务器每隔一段时间就自动执行一次 BGSAVE 命令,只要其中任意一个条件被满足,服务器就会执行 BGSAVE 命令。比如,服务器的默认 save 选项配置如下:
        save 900 1
        save 300 10
        save 60 10000
        这表示只要满足以下三个条件之一,BGSAVE 命令就会被执行:
        (1)服务器在 900 秒之内,对数据库进行了至少 1 次修改。
        (2)服务器在 300 秒之内,对数据库进行了至少 10 次修改。
        (3)服务器在 60 秒之内,对数据库进行了至少 10000 次修改。
        Redis 服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置 save 选项,服务器程序会根据 save 选项设置的保存条件来设置服务器状态 redisServer 结构的 saveparams 属性,该属性是一个数组,其中的每个元素都是一个 saveparam 结构,每个结构保存一个 save 选项设置的保存条件。
struct redisServer{
    /* ... */
    struct saveparam *saveparams;
    long long dirty;    // 修改计数器
    time_t lastsave;    // 上一次执行保存的时间
    /* ... */
};

struct saveparam{
    time_t seconds;    // 秒数
    int changes;       // 修改数
};

        除了 saveparams 数组之外,服务器状态还维护着一个 dirty 计数器和一个 lastsave 属性。其中,dirty 计数器记录距离上一次成功执行 SAVE 或者 BGSAVE 命令后,服务器又对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除和更新等操作)。而 lastsave 属性是一个 UNIX 时间戳,记录了服务器上一次成功执行 SAVE 或 BGSAVE 命令的时间。
        Redis 的服务器周期性操作函数 serveCron 默认每隔 100 毫秒就会执行一次,它用于对正在运行的服务器进行维护,它的其中一项工作就是检查 save 选项所设置的保存条件是否已经满足,只要有一个满足,它就会执行 BGSAVE 命令。这个过程的伪代码如下。
def serverCron():
    # ...
    for saveparam in server.saveparams:
        interval = unixtime_now() - server.lastsave;
        if server.dirty >= saveparam.changes and interval > saveparam.seconds:
            BGSAVE()
            break
    # ...

        以上就是 Redis 服务器根据 save 选项所设置的保存条件来自动执行 BGSAVE 命令,从而进行间隔性数据保存的实现原理。

参考书籍:《Redis 设计与实现》第 10 章——RDB 持久化。