Redis持久化RDB原理+伪代码实现
Redis 分别提供了 RDB 和 AOF 两种持久化机制, 本章首先介绍 Redis 服务器保存和载入 RDB 文件的方法,重点说明 SVAE 命令和 BGSAVE 命令的实现方式。之后,本章会继续介绍 Redis 服务器自动保存功能的实现原理。各个组成部分,并说明这些部分的结构和含义。在本章的最后,我们将对实际的 RDB 文件进行分析和解读,将之前学到的关于 RDB 文件的知识投人到实际应用中。其中还会查看有些伪代码方便理解,本文来源 redis设计与实现,关于 redis 持久化知识比较重要,所以直接看的书,避免走弯路,以这篇文章记录一下。
基本介绍
RDB 持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个 RDB 文件中。所生成的 RDB 文件是一个经过压缩的二进制文件,通过该文件可以还
原生成RDB文件时的数据库状态。
RDB文件的创建与载入
有两个 Redis 命令可以用于生成 RDB 文件,一个是 SAVE ,另一个是 BGSAVE
SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求BGSAVE命令会派生一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
创建 RDB 文件的实际工作由 rdb.c/rdbsave 函数完成, SAVE 命令和 BGSAVE 命令会以不同的方式调用这个函数,通过以下伪代码可以明显地看出这两个命令之间的区别:
def SAVE(): rdbSave() def BGSAVE(): pid = fork() if pid == 0: # 子进程保存 RDB rdbSave() elif pid > 0: # 父进程继续处理请求,并等待子进程的完成信号 handle_request() else: # pid == -1 # 处理 fork 错误 handle_fork_error()
RDB 文件的载入工作是在服务器启动时自动执行的,载入 RDB 文件的实际工作由 rdb.c/rdbLoad 函教完成,所以 Redis 并没有专门用于载人 RDB 文件的命令,只要 Redis 服务器在启动时检测到 RDB 文件存在,它就会自动载入 RDB 文件。
看到以上输出就是在成功载入 RDB 文件打印的,另外值得一提的是,因为 AOF 文件的更新频率通常比 RDB 文件的更新频率高,所以:
- 如果服务器开启了
AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。 - 只有在
AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态。
下图是服务器载入文件时的判断流程:
SAVE和BGSAVE命令执行时服务器的不同状态
SAVE
前面提到过,当 SAVE 命令执行时, Redis 服务器会被阻塞,所以当 SAVE 命令正在执行时,客户端发送的所有命令请求都会被拒绝。
只有在服务器执行完 SAVE 命令、重新开始接受命令请求后,客户端发送的命令才会被处理。
BGSAVE
因为 BGSAVE 命令的保存工作是由子进程执行的,所以在子进程创建 RDB 文件的过程中, Redis 服务器仍然可以继续处理客户端的命令请求,但是,在 BGSAVE 命令执行期间,服务器处理 SAVE 、 BGSAVE 、 BGREWRITEAOF 三个命令的方式会和平时有所不同。
首先,在 BGSAVE 命令执行期间,客户端发送的 SAVE 命令会被服务器拒绝,服务器禁止 SAVE 命令和 BGSAVE 命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个 rdbSave函数 调用,防止产生竞争条件。
其次,在 BGSAVE 命令执行期间,客户端发送的 BGSAVE 命令会被服务器拒绝,因为同时执行两个 BGSAVE 命令也会产生竞争条件
最后, BGREWRITEAOF 和 BGSAVE 两个命令不能同时执行:
- 如果
BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。 - 如果
BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。
因为 BGSAVE 和 BGREWRITEAOF 两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑。并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写人操作,这怎么想都不会是一个好主意。
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成
自动间隔性保存
这个就是利用 BGSAVE 命令,设置相关条件执行命令,例如我们 redis 一般有如下配置:
save 900 1 save 300 10 save 60 10000
以上配置的解释
- 服务器在 900 秒之内,对数据库进行了至少 1 次修改
- 服务器在 300 秒之内,对数据库进行了至少 10 次修改
- 服务器在 60 秒之内,对数据库进行了至少 10000 次修改
自动保存伪代码
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命令的时间。
例如:
SET message "hello" # 程序此时将 dirty计数器增加1 SADD database Redis MongoDB MariaDB # 程序此时将 dirty计数器增加3
上图就展示了服务器状态中包含的 dirty 计数器和 lastsave 属性,说明如下:
dirty计数器的值为 123 ,表示服务器在上次保存之后,对数据库状态进行了 123 次修改lastsave属性则记录了服务器上次执行保存操作的时间戳
检查保存条件是否满足
Redis 的服务器周期性操作函数 servercron 默认每隔100毫秒就会执行一次,该函条件是否已经满足,如果满足的话,就执行 BGSAVE 命令。
以下伪代码展示了 servercron 函教检查保存条件的过程:
def serverCron(): # 遍历所有条件 for saveparam in server.saveparams: # 计算距离上次执行保存操作有多少秒 save_interval = unixtime_now() - server.lastsave # 如果数据库状态的修改次数超过条件所设置的次数 并且距离上次保存的时间超过条件所设置的时间 那么执行保存操作 if server.dirty >= saveparam.changes and save_interval > saveparam.seconds: BGSAVE()
通过以上代码可以得知,程序会遍历并检查 saveparams 数组中所有保存条件,只要有任意一个条件被满足,那么服务器就会执行 BGSAVE 命令
RDB文件结构
以下展示了一个完整 RDB 文件所包含的各个部分
REDIS
RDB 文件的最开头是 REDIS 部分,这个部分的长度为5字节,保存著"REDIS"五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否 RDB 文件。
db_version
db_version 一个四字节长的以字符表示的整数,记录了该文件所使用的 RDB 版本号。目前的 RDB 文件版本为 0009 。因为不同版本的 RDB 文件互不兼容,所以在读入程序时,需要根据版本来选择不同的读入方式。
databases
databases 部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据:
- 如果服务器的数据库状态为空(所有教据库都是空的),那么这个部分也为空,长度为0字节
- 如果服务器的数据库状态为非空(有至少一个数据库非空),那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也会有所不同。
EOF
EOF 常量的长度为1字节,这个常量标志着 RDB 文件正文内容的结束,当读入程序遇到这个值的时候,它知道所有数据库的所有键值对都已经载入完毕了
CheckSum
check_sum 是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对 REDIS 、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载人RDB文件时,会将载人数据所计算出的校验和与 check_sum 所记录的校验和进行对比,以此来检查 RDB 文件是否有出错或者损坏的情况出现。
从 Version 5 开始,如果在配置文件中开启 rdbchecksum yes ,会在 RDB 文件的结尾处,用 8 个字节, CRC64 计算整个文件内容的检验和。
举个例子
这个是我最新拉的 redis ,数据为空,我们依次进行分析: od -c rdb.rdb
0000000 R E D I S 0 0 0 9 372 \t r e d i s 0000020 - v e r 005 6 . 0 . 9 372 \n r e d i 0000040 s - b i t s 300 @ 372 005 c t i m e 302 0000060 M 301 264 _ 372 \b u s e d - m e m 302 @ 0000100 345 \f \0 372 \f a o f - p r e a m b l 0000120 e 300 \0 377 g 311 203 274 200 T 211 376 0000134
实际上这些字段是 AOF 和 RDB 通用部分的文件头内容:
- 头5字节固定为
REDIS - 第6~9共四字节为
RDB版本号 - 接下来为
redis-ver和它的值,即redis版本 - 接着
redis-bits和它的值,即redis的位数,值为32或64 - 接着为
ctime和它的值,RDB文件创建时间 - 接着为
used-mem使用内存大小 - 最后是
aof-preamble和它的值,值为0或1,1表示RDB有效。
但 RDB 文件头在 aof-preamble 之前多了如下三项:
repl-stream-db在server.master客户端中选择的数据库repl-id当前实例replication IDrepl-offset当前实例复制的偏移量
你学会了
RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对教据。SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。BGSAVE令由子进程执行保存操作,所以该命令不会阻塞服务器。- 服务器状态中会保存所有用
save选项设置的保存条件,当任意一个保存条件被潮足时,服务器会自动执行BGSAVE命令。 RDB文件是一个经过压缩的二进制文件,由多个部分组成。- 对于不同类型的键值对,
RDB文件会使用不同的方式来保存它们。




