什么是RDB持久化

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。

RDB文件的创建与载入

redis中有两个命令可以生成RDB文件,分别是SAVE命令以及BGSAVE命令。

SAVE命令会阻塞redis进程,直到RDB文件生成后,redis才可以继续处理请求。

BGSAVE命令会委派一个子进程,由子进程来创建RDB文件,然后主进程继续处理请求。

RDB文件的载入是在服务器启动时自行载入,没有命令。

注意:如果同时开启AOF和RDB这两种持久化方法,那么会优先使用AOF。

执行BGSAVE时服务器状态

在执行BGSAVE命令时,为了避免竞争,服务器在此期间收到SAVE命令和BGSAVE命令会直接拒绝。

而BGREWRITEAOF不能和BGSAVE命令一起执行。

如果此时服务器正在执行BGSAVE命令,那么他会把BGREWRITEAOF延迟到BGSAVE命令执行完在执行。如果在执行BGREWRITEAOF,那么BGSAVE命令会被拒绝。

自动间隔设计

redis允许用户设置每隔一段时间执行一次BGSAVE命令。使用save命令即可。具体操作如下:

1
2
// 意味着每900秒,有1次修改就执行一次BGSAVE命令。
save 900 1

如何保存

通过save命令所设置的值会保存在redisServer中的saveparams属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct redisServer {
// 一个数组,保存着服务器中所有的数据库
redisDb *db;

// 服务器数据库的数量
int dbnum;

// 记录了保存条件的数组
struct saveparam *saveparam;
};

struct saveparam {
// 秒数
time_t seconds;

// 修改书
int changes;
};

saveparam是一个数组,里面每一个元素都是一个saveparam的结构体,保存了上述命令设置的时间以及修改数。

比如执行了以下命令

1
2
3
save 900 1
save 300 10
save 60 10000

那么saveparams中就会如下图所示

image-20230324140839313

dirty计数器和lastsave

除了saveparams,redisServer还维护了一个dirty计数器和lastsave属性,其中dirty计数器记录了距离上一次成功执行SAVE或者BGSAVE命令后,服务器对数据库(所有的db,即db数组中每一个)进行了多少次修改(增删改),lastsave命令是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

具体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct redisServer {
// 一个数组,保存着服务器中所有的数据库
redisDb *db;

// 服务器数据库的数量
int dbnum;

// 记录了保存条件的数组
struct saveparam *saveparam;

// 修改计数器
long long dirty;

// 上一次执行保存的时间
time_t lastsave;
};

其中dirty在每次执行完SAVE或者BGSAVE后,会被置0。

RDB文件结构

一个具体的RDB文件结构如下图所示:

image-20230324142312205

RBD文件开头是REDIS部分,这里保存着”REDIS”五个字符,用于标识文件是一个RDB文件。

db_version长度为4字节,是一个用字符串表示的整数,记录RDB文件的版本号。

databases部分包含着零个或多个数据库,以及各个数据库的键值对。其中,如果所有数据库都为空(db数组中的所有数据库),那么这里也为空。如果至少有一个非空,那么根据数据库保存的数据键值对数量,类型和内容不同,这里也有所不同。

EOF常量的长度为1,标志着RDB文件正文内容的结束。

check_num是一个8字节的无符号整数,保存着校验和。这个校验和是通过对前四部份内容进行计算得出的,服务器在载入RDB文件时,会根据前四部分进行计算然后以check_num进行对比,来校验是否出错。

databases部分

假如redisServer的db部分有2个数据库不为空,那么databases结构如下:

image-20230324143315822

每个非空数据库在RDB文件中都可以保存为SELECTDB、db_number、key_value_pairs三部分。

image-20230324144419555

SELECTDB常量的长度为1字节,用于当程序直到,接下来读入的是一个数据库号码。

db_number保存的是一个数据库号,根据号码大小不同,可以是1字节,2字节或者5字节。读到这个数据后会立即执行一个SELECT命令,进行数据库切换,以保证后续载入是正确的。

key_value_pairs保存了数据库中所有键值对数据,如果键值对带有过期时间,那么过期时间也会被保存。

key_value_pairs

该部分保存具体的键值对,如果不带过期时间,则由TYPE,key,value三部分组成。

其中TYPE记录了value的类型。key总是一个字符串对象。

结构如下:

image-20230324145255638

带过期时间的多了一个EXPIRETIME_MS和ms属性。

EXPIRETIME_MS用于告知接下来读入将是一个以毫秒为单位的过期时间,ms是一个UNIX时间戳,记录着过期时间。

image-20230324145440827

下面介绍key_value_pairs的type不同编码所对应的value

字符串对象

在服务器打开了RDB文件压缩功能的情况下,如果字符串的长度大于20字节,那么这个字符串会被压缩后保存。

不压缩和压缩后的结构分别如下:

image-20230324150428100

image-20230324150437915

压缩后的REDIS_RDB_ENC_LZF代表开启了LZF压缩算法,需要通过后边三个参数来进行解压。

image-20230324150539948

列表对象

image-20230324151159563

list_length记录了列表的长度,即有多少个元素。后边是每个具体的元素。一个具体的结构如下:

image-20230324152531691

集合对象

集合对象和上面的列表对象一样。

哈希表对象

结构如下

image-20230324152830195

一个具体的例子:

image-20230324152856443