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 配置
1 2 3 4 5 6 7 8 9 10 11 12 |
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 等命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* 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 命令
1 2 3 4 5 6 7 8 9 10 11 |
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 命令
1 2 3 4 5 6 7 8 9 10 11 |
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 命令,是立马开启一下子进程,后台进行刷盘
立马返回信息给你客户端
文章作者 jc3wish
上次更新 2019-12-02