Redis高级之底层源码6——数据持久化(RDB方式)

本文介绍了Redis持久化方案,重点阐述RDB持久化。Redis是内存型数据库,为防数据丢失需持久化,有RDB和AOF两种方案,4.0后支持混合持久化。RDB是默认方案,可按配置策略或手动用save、bgsave触发,还介绍了其触发方式、后台触发条件及备份应用场景。

1 概述

        Redis是一种内存型数据库,即服务器在运行时,系统为期分配了一部分内存来存储数据,当服务区突然宕机,那么数据库里面的数据将会丢失,为了应对这种情况,必须通过持久化的方式将数据从内存保存到磁盘中,如下图所示:

        Redis持久化存储有两种方案:RDB(RedisDataBase)和AOF(Append-Only File),其中RDB是将内存中数据的快照存储到磁盘中,AOF则是通过日志记录Redis内的所有操作。Redis4之后支持AOF+RDB混合持久化的方式,结合两者的有点,可以通过aof-use-rdb-preamble配置开启混合持久化功能的开关。

        Redis提供了不同范围的持久化选项:

        RDB持久化按指定的时间间隔存储数据集的时间点快照。

        AOF持久化会记录服务器接收的每个写入操作,这些操作将在服务器启动时再次“播放”,以重建原始数据集。使用Redis协议相同的格式记录命令,并且仅采用追加方式。当日志过大时,Redis会在后台重写日志。

        Redis持久化的RDB和AOF示意图,如下:

 

2 RDB持久化

        2.1 RDB概述

         RDB是将Redis内存中数据的快照存储到磁盘内,是Redis默认的持久化方案。RDB持久化默认有三种策略,可以在redis.conf中配置,会以一段时间内达到执行修改的次数为规则来触发快照操作,快照文件名为dump.rdb。每当Redis服务器重启的时候都会从该文件中把数据加载到内存中。

        RDB持久化除了可以根据配置中的策略来触发外,还可以使用save和bgsave命令手动触发。这两个命令的区别在于save会阻塞服务器进程。在执行save命令的过程中,服务器不能处理任何请求,但是bgsave命令会通过一个字进行在后台处理数据的RDB持久化。本质上save和bgsave调用的都是rdbsave函数,所以Redis不允许save和bgsave同时执行,也是为了避免RDB文件数据出现不一致的问题。

2.2 save触发方式

        save命令源码如下:

void saveCommand(client *c) {
    //bgsave命令执行时,不能执行save命令
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
        return;
    }
    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    //调用rdbsave函数进行备份
    if (rdbSave(server.rdb_filename,rsiptr) == C_OK) {
        addReply(c,shared.ok);
    } else {
        addReply(c,shared.err);
    }
}

        save命令将内存数据的镜像保存为RDB文件。由于Redis是以单线程方式执行命令,因此save命令执行期间会阻塞Redis服务进程,Redis服务不再处理任何命令,直到RDB文件创建完成为止。一般不建议使用save命令执行持久化。如下图:

2.3 bgsave触发

        执行bgsave命令时,Redis服务器的处理流程如下图: 

        bgsave命令使用CopyOnWrite机制来执行写时的复制操作,由一个子进程将内存中的最新数据依次写入临时文件,此时父进程仍旧处理客户端的操作,当字进程执行完毕后再将该临时文件重命名为dump.rdb。所以,无论bgsave命令是否执行成功,dump.rdb都不会收到影响。因此建议使用bgsave。

bgsave命令的源码如下:

void bgsaveCommand(client *c) {
    int schedule = 0;

    /* 当AOF正在执行时,bgsave会在AOF完成后再执行 */
    if (c->argc > 1) {
        //参数只能是schedule
        if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"schedule")) {
            schedule = 1;
        } else {
            addReply(c,shared.syntaxerr);
            return;
        }
    }

    rdbSaveInfo rsi, *rsiptr;
    rsiptr = rdbPopulateSaveInfo(&rsi);
    //bgsave命令正在执行时,不会执行save命令
    if (server.rdb_child_pid != -1) {
        addReplyError(c,"Background save already in progress");
    } else if (hasActiveChildProcess()) {
        if (schedule) {
            server.rdb_bgsave_scheduled = 1;
            addReplyStatus(c,"Background saving scheduled");
        } else {
            addReplyError(c,
            "Another child process is active (AOF?): can't BGSAVE right now. "
            "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
            "possible.");
        }
    }
    //否则调用rdbSaveBackground执行备份操作
    else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
        addReplyStatus(c,"Background saving started");
    } else {
        addReply(c,shared.err);
    }
}

执行bgsave命令时,Redis还能继续处理客户端的操作,如下图

2.4 后台触发

        以下是RDB持久化的配置文件:

#定时持久化规则
save 900 1
save 300 10
save 60 10000

#默认值是yes,当启用了RDB且最后一次在后台保存数据失败,Redis是否停止接收数据
#yes代表可以继续写入数据。no代表不会写入并通知用户持久化出现错误
stop-writes-on-bgsave-error yes

#持久化的数据是否进行压缩
rdbcompression yes

#存储的快照是否进行CRC64算法的数据校验,如果希望获取最大的性能提升,可以关闭此功能
rdbchecksum yes

#设置快照的文件名,默认是dump.rdb
dbfilename dump.rdb

        RBD持久化策略默认有三种方式:

        1、在60秒内有10000次操作即触发RDB持久化。

        2、没有满足上面条件时,在900秒内有1次操作即触发持久化。

        3、没有满足第二种条件时,在300秒内有10次操作即触发持久化。

        同样,可以手动修改改参数或新增策略。

#在seconds秒内有changes次数据操作就会触发RDB持久化
save <seconds> <changes>

        触发持久化源码如下:

struct saveparam {
    //记录秒数
    time_t seconds;
    //记录操作次数,每次执行完RDB后清零
    int changes;
};

/* 持久化相关的对象*/
    
    //记录最后一次save之后的数据变化
    long long dirty;                /* Changes to DB from the last save */
    //用于在失败的bgsave上还原脏数据
    long long dirty_before_bgsave;  /* Used to restore dirty on failed BGSAVE */
    //RDB进程的子进程号
    pid_t rdb_child_pid;            /* PID of RDB saving child */
    //RDB操作存储的数据点
    struct saveparam *saveparams;   /* Save points array for RDB */
    //保存存储的点数
    int saveparamslen;              /* Number of saving points */
    //RDB的文件名称
    char *rdb_filename;             /* Name of RDB file */
    //是否压缩文件
    int rdb_compression;            /* Use compression in RDB? */
    //是否对文件进行校验
    int rdb_checksum;               /* Use RDB checksum? */
    int rdb_del_sync_files;         /* Remove RDB files used only for SYNC if
                                       the instance does not use persistence. */
    //记录最后一次保存成功的时间
    time_t lastsave;                /* Unix time of last successful save */
    //上次尝试保存的UNIX时间
    time_t lastbgsave_try;          /* Unix time of last attempted bgsave */
    //上次RDB操作运行所用的时间
    time_t rdb_save_time_last;      /* Time used by last RDB save run. */
    //当前RDB操作开始时间
    time_t rdb_save_time_start;     /* Current RDB save start time. */
    //标记是否可以执行RDB操作
    int rdb_bgsave_scheduled;       /* BGSAVE when possible if true. */
    //RDB 子进程操作类型
    int rdb_child_type;             /* Type of save by active child. */
    //最后一次RDB 操作的结果
    int lastbgsave_status;          /* C_OK or C_ERR */
    //如果无法保存,则不允许写入
    int stop_writes_on_bgsave_err; 

        从上面源码可以看出计数器记录了在上一次成功的持久化后Redis执行了多少次写操作,其值在每次写操作之后都加1,在满足配置条件并完成持久化后记录就会清零。Redis还有一个周期性操作函数,默认每隔100ms执行一次,其中的一项工作就是检查自动触发bgsave命令的条件是否成立。下面是一些操作的相关源码:

for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            /* 根据配置的时间和执行时间来判断是否触发bgsave操作 */
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 CONFIG_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == C_OK))
            {
                serverLog(LL_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveInfo rsi, *rsiptr;
                rsiptr = rdbPopulateSaveInfo(&rsi);
                rdbSaveBackground(server.rdb_filename,rsiptr);
                break;
            }
        }

        RDB文件结构如下图:

2.5 RDB备份的应用场景

        RDB 是Redis中按时间点保存的紧凑数据文件,非常适合备份。比如,在最近的24小时内每小时将数据存档一次(保存为RDB文件),并在30天内每天保存一次RDB 快照,这样就可以在发生灾难时轻松把数据集还原到在不同时间点备份的数据。

        RDB 是一个紧凑文件,对于灾难恢复非常有用,并可传输到远程的数据中心。

        RDB 最大限度地提高了Redis的性能,因为Redis父进程为了持久化所做的唯一工作就是分派一个子进程,把其余的工作都交给子进程去做,父进程永远不会执行类似于磁盘I/O这类操作,这样可以让Redis具有更快还原数据的能力。

        如果需要最大限度地减少数据丢失的可能,那么使用RDB 非常适合。可以在生成RDB 的时候设置不同的规则(例如,五分钟之内对数据集进行100次写入,产生多个保存点),通常每个5分钟或更长时间创建一次RDB 快照。如果Redis在没有正确关闭的情况下停止工作,则最多丢失一次备份操作后的一些数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geminigoth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值