Redis
是一个键值对数据库服务器,服务器中通常包含着任意个非空数据库,而每个非空数据库中有可以包含任意个键值对,为了方便起见,我们将服务器中非空数据库以及他们的键值对统称为数据库状态。
因为Redis
是内存数据库,它将自己的数据库状态存储在内存里面,所以如果不想办法将内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出。服务器中的数据库状态也就会消失不见。
为了解决这个额问题,Redis
提供了RDB
持久化功能,这个功能可以将Redis
在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。
RDB
持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB
文件中。
RDB
持久化功能所生成的RDB
文件是要一个经过压缩的二进制文件,通过该文件可以还原生成RDB
文件时的数据库状态。
因为RDB文件时保存在硬盘里面的,所以即使Redis
服务器进程退出,甚至运行Redis
服务器的计算机停机,但只要RDB
文件仍然存在,Redis
服务器就可以用它来还原数据库状态。
10.1 RDB文件的创建与载入
有两个Redis
命令可以用于生成RDB
文件,一个是SAVE
,另一个是BGSAVE
。
SAVE
命令会阻塞Redis
服务器进程,直到RDB
文件创建完毕位置,在服务器进行阻塞期间,服务器不能处理任何命令请求:
BGSAVE
命令会派生出一个子进程,然后由子进程负责创建RDB
文件,服务器进程(父进程)急需处理命令请求:
创建RDB
文件的实际工作有rdb.c/rdbSave
函数完成,SAVE
命令和BGSAVE
命令会以不同的方式调用这个函数。
和使用SAVE
命令或者BGSAVE
命令创建RDB
文件不同,RDB
文件的载入工作是在服务器启动时自动执行的,所以Redis
并没有专门用于载入RDB
文件的命令,只要Redis
服务器在启动时检测到RDB
文件的存在,它就会自动载入RDB
文件。
另外,因为AOF
文件的更新频率通常比RDB
文件的更新频率高,所以:
- 如果服务器开启了
AOF
持久化功能,那么服务器会优先使用AOF
文件来还原数据库状态。 - 只有在
AOF
持久化功能处于关闭状态时,服务器才会使用RDB
文件来还原数据库状态。
10.1.1 SAVE命令执行时的服务器状态
当SAVE
命令执行时,Redis
服务器会被阻塞,所以当SAVE
命令正在执行时,客户端发送的所有命令请求都会被拒绝。
只有在服务器执行完SAVE
命令、重新开始接受命令请求之后,客户端发送的命令才会被处理。
10.1.2 BGSAVE命令执行时的服务器状态
BGSAVE
命令的保存工作是由子进程执行的,所以在子进程创建RDB
文件的过程中,Redis
服务器仍然可以继续处理客户端的命令请求,但是,在BGSAVE
命令执行期间,服务器处理SAVE
、BGSAVE
、BGREWRITEAOF
三个命令的方式会和平时有所不同。
首先,在BGSAVE
命令执行期间,客户端发送的SAVE
命令会被服务器拒绝,服务器禁止SAVE
命令和BGSAVE
命令同时执行是为了避免父进程和子进程通知执行两个rdbSave
调用,防止产生竞争条件。
其次,在BGSAVE
命令执行期间,客户端发送的BGSAVE
命令会被服务器拒绝,因为同时执行两个GBSAVE
命令也会产生竞争条件。
最后,BGREWIRETEAOF
和BGSAVE
两个命令不能同时执行:
- 如果
BGSAVE
命令正在执行,那么客户端发送的BGREWRIETEAOF
命令会被延迟到BGSAVE
命令执行完毕之后执行。 - 如果
BGREWRITEAOF
命令正在执行,那么客户端发送的BGSAVE
命令会被服务器拒绝
这个两个命令并没有冲突,只是同时执行大量的磁盘写入操作,对数据库是一个非常大的压力。
10.1.3 RDB文件载入时的服务器状态
服务器在载入RDB
文件期间,会一直处于阻塞状态,直到载入工作完成为止。
10.2 自动间隔保存
因为BGSAVE
命令可以在不阻塞服务器进程的情况下执行,所以Redis
允许用户通过设置服务器配置的save
选项,让服务器每隔一段时间自动执行一个BGSAVE
命令。
用户可以通过save
选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE
命令
save 900 1
save 300 10
save 60 10000
- 服务器在900秒之内,对数据库进行了至少1次修改
- 服务器在300秒之内,对数据库进行了至少10次修改
- 服务器在60秒之内,对数据库进行了至少10000次修改
10.2.1 设置保存条件
当Redis
服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save
选项,如果用户没有主动设置save
选项,那么服务器会为save
选项设置默认条件:
save 900 1
save 300 10
save 60 10000
接着,服务器程序根据save
选项所设置的保存条件,设置服务器状态redisServer
结构的saveparams
属性:
struct redisServer{
//...
//记录了保存条件的数组
struct saveparam *saveparams;
//...
}
saveparams
属性是一个数组,数组中的每隔元素都是一个saveparam
结构,每个saveparams
结构都保存了一个save
选项设置的保存条件:
struct saveparams{
//秒数
time_t seconds;
//修改书
int changes;
};
10.2.2 dirty计算器和lastsave属性
除了saveparams
数组之外,服务器状态还会维持着一个dirty
计数器,以及一个lastsave
属性:
-
dirty
计数器记录距离上一次成功执行SAVE
命令或者BGSAVE
命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作) -
lastsave
属性是一个UNIX
时间戳,记录了服务器上一次成功执行SAVE
命令或者BGSAVE
命令的时间。
struct redisServer{
//...
//修改计算器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
//...
};
当服务器成功执行一个数据库修改命令之后,程序就会对dirty
计数器进行更新:命令修改了多少次数据库,dirty
计数器的值就增加多少。
10.2.3 检查保存条件是否满足
Redis
的服务器周期性操作函数serverCron
默认每隔100
毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save
选项所保存条件是否已经满足,如果满足的话,就执行BGSAVE
命令。
程序会遍历并检查saveparams
数组中的所有保存条件,只要有任意一个条件被满足,那么服务器就会执行BGSAVE
命令。
10.3 RDB文件结构
完整RDB
文件所包含的各个部分如下
REDIS | db_version | databases | EOF | check_sum |
---|
RDB
文件的最开头是REDIS
部分,这个部分的长度为5
字节,保存着"REDIS"五个字符。程序可以在载入文件时,快速检查所载入的文件是否是RDB
文件。
db_version
长度为4
字节,它的值是一个字符串表示的整数,这个整数记录了RDB
文件的版本号。
databases
部分包含着零个或者任意多个数据库,以及各个数据库中的键值对数据。
- 如果服务器的数据状态为空(所有数据库都是空),那么这个部分也为空,长度为
0
字节 - 如果服务器的数据库状态为非空(有至少一个数据库非空),那么这个部分也为非空,根据数据库所保存键值对的数量、类型和内容不同,这个部分的长度也会有所不同。
EOF
常量的长度为1
字节,这个常量标志着RDB
文件正文内容的结束,当读入程序遇到这个值的时候,就知道数据库所有的键值对都已经载入完毕。
check_sum
是一个8
字节长的无符号证书,保存着一个校验和,这个校验和是程序通过对REDIS
、db_version
、databases
、EOF
四个部分的内容进行计算得出的。服务器在载入RDB
文件时,会将载入数据所计算出的校验和与check_sum
所记录的校验和进行对比,以此来检查RDB
文件是否有出错或者损坏的情况出现。
10.3.1 databases部分
一个RDB
文件的databases
部分可以保存任意多个非空数据库。
每个非空数据库在RDB
文件中都可以保存为SELECTDB
、db_number
、key_value_pairs
三个部分
SELECTDB | db_number | key_value_pairs |
---|
SELECTDB
常量的长度为1
字节,当读入程序遇到这个值的时候,知道接下来要读入的将是一个数据库号码。
db_number
保存着一个数据库号码,根据号码的大小不同,这个部分的长度可以是1
字节、2
字节或者5
字节。当程序读入db_number
部分之后,服务器会调用SELECT
命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。
key_value_pairs
部分保存了数据库中所有键值对数据,如果键值对带有过期时间,那么过期时间也会和键值对保存在一起。
详细如下:
|REDIS|db_version|SELECTDB|0|pairs|SELECTDB|3|pairs|EOF|check_sum|
10.3.2 key_value_pairs部分
RDB
文件中的每个key_value_pairs
部分都保存了一个或以上数量的键值对,如果键值对带有过期时间的话,那么键值对的过期时间也会被保存在内。
不带过期时间的键值对在RDB
文件中由TYPE
、key
、value
三部分组成。
TYPE | key | value |
---|
TYPE
记录了value
的类型,长度为1
字节,值可以是以下常量的其中一个:
REDIS_RDB_TYPE_STRING
REDIS_RDB_TYPE_LIST
REDIS_RDB_TYPE_SET
REDIS_RDB_TYPE_ZSET
REDIS_RDB_TYPE_HASH
REDIS_RDB_TYPE_LIST_ZIPLIST
REDIS_RDB_TYPE_SET_INTSET
REDIS_RDB_TYPE_ZSET_ZIPLIST
REDIS_RDB_TYPE_HASH_ZIPLIST
以上列出的每个TYPE
常量都代表了一种对象类型或者底层编码,当服务器读入RDB
文件中的键值对数据时,程序会根据TYPE
的值来决定如何读入和解释value
的数据。key
和value
分别保存了键值对的键对象和值对象:
-
key
为一个字符串对象,编码方式为REDIS_RDB_TYPE_STRING
- 根据
TYPE
类型的不同,以及保存内容长度的不同,保存value
的结构和长度也会有所不同。
带有过期时间的键值对在RDB
文件中的结构如下
EXPIRETIME_MS | ms | TYPE | key | value |
---|
-
EXPIRETIME_MS
常量的长度为1
字节,告知程序,接下来要读入的将是一个以毫秒为单位的过期时间 -
ms
是一个8字节的带符号整数,记录着一个以毫秒为单位的UNIX
时间戳,这个时间戳就是键值对的过期时间。
10.3.3 value的编码
本部分略过。(具体的实现,与代码层面或者原理无关)
10.4 分析RDB文件
本部分略过。(具体的实现,与代码层面或者原理无关)
网友评论