Redis RDB 持久化 实现原理


1. RDB 概念

官方解析

By default Redis saves snapshots of the dataset on disk, in a binary file called dump.rdb. You can configure Redis to have it save the dataset every N seconds if there are at least M changes in the dataset, or you can manually call the SAVE or BGSAVE commands.

本人解析

将内存中数据,以镜像的方式存储到 dump.rdb 二进制文件中

通过配置 N 秒刷了多少条数据进行触发

也可以通过 SAVE 或者 BGSAVE 命令进行触发

2. RDB 配置

save 900 1      #900 秒内如果至少有 1 个 key 的值变化,则保存

save 300 10    #300 秒内如果至少有 10 个 key 的值变化,则保存

save 60 10000  #60 秒内如果至少有 10000 个 key 的值变化,则保存

stop-writes-on-bgsave-error yes 

#当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据

rdbcompression yes  #是否进行压缩存储

rdbchecksum yes  #使用CRC64算法来进行数据校验

dbfilename dump.rdb #快照的文件名

dir ./  #快照文件的存放路径

3. 定时检查是否刷开子进程刷盘

1). 每次写命令执行的时候 变量 server.dirty ++

2). 默认情况下每100ms 触发一次定时器,检查是否满足配置条件

save 900 1

#900 秒内如果至少有 1 个 key 的值变化,则保存 save 300 10

#300 秒内如果至少有 10 个 key 的值变化,则保存 save 60 10000

#60 秒内如果至少有 10000 个 key 的值变化,则保存

3). 开启一个子进程将数据镜像刷到磁盘

开启的子进程是采用 copy on write 的技术, 在有写命令的时候,才会复制2份

4. RDB 刷盘过程

1). 创建一个 tmp-$pid.rdb 的临时文件

2). 按 RDB 存储协议 追加数据到 tmp-$pid.rdb 文件中

RDB 文件 最开头 5 个字节 是存储的是 REDIS 5个字母

再接下来的5个字节是RDB版本号,例如:0006

接着就按 Redis 数据库顺序根据不同 存储的类型刷数据了

不过什么类型,过期时间在前面,然后是就是数据类型,再接着就是key值,和 val值

当所有 db 都遍历完之后,接着写一下 255 这个数字,占1个字节

最后刷一个 cksum 值

3). 当所有数据追加到 tmp-$pid.rdb 完之后,对句柄进行close

4). 将 tmp-$pid.rdb 文件重命名为 dump.rdb

这里为什么要先写一个 tmp临时文件,写完之后再修改 dump.rdb 文件呢?

假如直接写 dump.rdb文件,假如中途写失败了呢?那文件不就损坏了么?!

5. RDB 刷盘失败

什么时候会刷盘失败?

A. 当上一次rdb刷盘还没有完成的时候,不会再开新的子进程刷盘 B. 开启子进程失败,子进程被kill 或者运行过程 异常退出

不管什么原因造成子进程退出 ,在子进程结束回调的时候都会修改server.lastbgsave_status=REDIS_ERR

6. stop-writes-on-bgsave-error

在 开启了 stop-writes-on-bgsave-error yes

并且 上一次 RDB 刷盘之无,刷盘失败了,则会阻塞 写和PING 等命令

/* Don't accept write commands if there are problems persisting on disk

    * and if this is a master instance. */

    if (((server.stop_writes_on_bgsave_err &&

          server.saveparamslen > 0 &&

          server.lastbgsave_status == REDIS_ERR) ||

          server.aof_last_write_status == REDIS_ERR) &&

        server.masterhost == NULL &&

        (c->cmd->flags & REDIS_CMD_WRITE ||

        c->cmd->proc == pingCommand))

    {

        flagTransaction(c);

        if (server.aof_last_write_status == REDIS_OK)

            addReply(c, shared.bgsaveerr);

        else

            addReplySds(c,

                sdscatprintf(sdsempty(),

                "-MISCONF Errors writing to the AOF file: %s\r\n",

                strerror(server.aof_last_write_errno)));

        return REDIS_OK;

    }

7. 手动触发 RDB 镜像生成

7.1 SAVE 命令

void saveCommand(redisClient *c) {

    if (server.rdb_child_pid != -1) {

        addReplyError(c,"Background save already in progress");

        return;

    }

    if (rdbSave(server.rdb_filename) == REDIS_OK) {

        addReply(c,shared.ok);

    } else {

        addReply(c,shared.err);

    }

}

SAVE 命令,是阻塞式的

是由当前主线程进行刷盘,而不是开子进程刷盘

刷完之后再返回给客户端

7.2 BIGSAVE 命令

void bgsaveCommand(redisClient *c) {

    if (server.rdb_child_pid != -1) {

        addReplyError(c,"Background save already in progress");

    } else if (server.aof_child_pid != -1) {

        addReplyError(c,"Can't BGSAVE while AOF log rewriting is in progress");

    } else if (rdbSaveBackground(server.rdb_filename) == REDIS_OK) {

        addReplyStatus(c,"Background saving started");

    } else {

        addReply(c,shared.err);

    }

}

BIGSAVE 命令,是立马开启一下子进程,后台进行刷盘

立马返回信息给你客户端

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。