RDB持久化

Redis是一个键值对数据库服务器,服务器中包含任意个非空数据库,每个数据库中又可能包含任意个键值对,我们将服务器中的非空数据库及数据库中的键值对数据统称为数据库状态。

Redis是一个内存级别的数据库,数据库状态存储在内存中,如果不将数据库状态持久化到磁盘中保存,一旦服务器进程退出,服务器中的数据库状态也会消失不见。RDB持久化则可以解决该问题,RDB持久化是将某个时间点的数据库状态保存到一个RDB文件中。RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。

RBD持久化即可以手动执行,也可以通过服务器配置文件中的配置项设置定期执行。

1. RDB文件的创建与载入

Redis提供了两个命令用于生成RDB文件,一个是SAVE,另一个是BGSAVE;

SAVE命令会阻碍服务器进程,直到RDB文件创建完成为止,并且在服务器进程阻塞期间,服务器不能处理任何请求;BGSAVE命令会派生出一个子进程,由子进程负责创建RDB文件,服务器进程可以继续处理客户端发送的命令请求;RDB文件的创建工作是由rdbSave()函数完成,SAVE命令和BGSAVE命令是通过不同的方式调用该函数;

RDB文件的载入工作是在服务器启动时自动执行的,Redis没有提供专门用于载入RDB文件的命令。Redis服务器启动时如果检测到RDB文件存在,则会自动载入RDB文件,还原数据库的状态。(DB loaded from disk;)

由于AOF文件的更新频率高于RDB文件,因此如果服务器开启AOF持久化功能,则服务器会优先使用AOF文件还原数据库状态;只有在AOF持久功能关闭的情况下,服务器才会使用RDB文件还原数据库状态;载入RDB文件的工作是由rdbLoad()函数完成;

1.1 BGSAVE命令执行时数据库的状态

服务器在执行BGSAVE命令期间,虽然可以继续处理客户端发送的命令请求,但是在处理SAVE、BGSAVE、BGREWRITEAOF命令时会与之前有所不同;

在执行BGAVE命令期间,如果服务器收到客户端发送的SAVE命令,服务器会拒绝执行;这是由于SAVE命令和BGSAVE命令都是执行rdbSAVE()函数,如果同时执行,则会出现竞争条件;

在执行BGSAVE命令期间,如果服务器收到客户端发送的BGSAVE命令,服务器会拒绝执行,原因同上;

在执行BGSAVE命令期间,如果服务器收到客户端发送的BGREWRITEAOF命令,服务器会将该命令延迟到BGSAVE命令后执行;反之,如果在BGREWRITEAOF命令执行期间,收到客户端发送的BGSAVE命令,服务器则会拒绝执行;这是由于BGWRITEAOF命令和BGSAVE命令都由派生出的子进程负责执行,并且这两个子进程都涉及大量的磁盘写入操作,如果同时执行,会严重影响Redis服务器的性能;

1.2 RDB文件载入时服务器的状态

载入RDB文件时,服务器会一直处于阻塞状态,直到文件载入完成为止;

2. 自动间隔性保存

除了通过SVAE和BGSAVE命令手动生成RDB文件之外,还可以通过在服务器配置文件中设置SAVE选项,让服务器定期执行BGSAVE命令,生成RDB文件;

2.1 设置保存条件
 1 struct redisServer{
 2     struct saveparam *saveparam;
 3     long long dirty;
 4     time_t lastSave;             
 5     ......
 6 }
 7 struct saveParam{
 8     time_t seconds;
 9     int changes;
10 }

用户在服务器配置文件中设置的save选项值,会被保存到服务器状态redisServer的saveParam数组中,saveParam数组中的元素均为自定义的saveParam结构,每个结构中都保存了save选项设置的时长和修改次数;

除此之外,redisServer结构中还使用dirty计数器,记录上一次成功执行SAVE命令或者BGSAVE命令之后,服务器状态发生了多少次修改;以及使用lastSave属性,记录上一次成功执行SAVE命令或者BGSAVE命令的UNIX时间戳;

2.2 检查保存条件是否满足

REDIS服务器的周期性操作函数serverCron会每隔100ms执行一次,该函数主要对正在运行的服务器进行维护,其中一项工作就是检查save选项设置的持久化条件是否满足,如果满足,则执行BGSAVE命令;

 1 def serverCron():
 2     # ...
 3     # 遍历所有save选项
 4     for saveparam in redisServer.saveparam:
 5         #计算距离上一次持久化操作过了多少秒
 6         save_interval = unixtime_now() - redisServer.lastSave;
 7         # 判断持久化条件是否满足
 8         if redisServer.dirty >= saveparam.changes and save_interval > saveparam.seconds:
 9             BGSAVE()
10     # ...

3. RDB文件结构

RDB文件的完整结构如下所示:

REDIS  db_version databases EOF check_sum

REDIS : RDB文件的开头部分,长度为5字节,保存着“REDIS”五个字符;通过这五个字符,程序可以直到载入的文件是否为RDB文件;

db_version : 长度为4字节,值是一个字符串表示的整数,含义为RDB文件的版本号;

databases : 包含零个或任意多个非空数据库,以及数据库中的键值对数据;

EOF : 常量,长度为1字节,标志着RDB文件正文内容的结束;当程序读到该值,则知道数据库中的所有键值对都已经载入完毕;

check_sum : 是一个8字节长的无符号整数,保存着一个校验和;该值是通过对REDIS、db_version、databases、EOF四部分计算得出的;服务器在载入RDB文件时,会根据载入数据计算出校验和与check_sum记录的值做对比,以此来检查RDB文件是否出错或者损坏;

databases部分非空数据库的完整结构:

SELECTDB db_number key_value_pairs

SELECTDB : 常量,长度为1字节;当程序读入该值,则知道接下来读取的是一个数据库号码;

db_number : 保存着一个数据库号码,根据号码大小的不同,该部分长度可以为1字节、2字节或者5字节;当程序读入该值后,会调用SELECT命令,进行数据库的切换,保证键值对可以载入到正确的数据库中;

key_value_pairs : 保存数据库中所有键值对数据,如果键值对带有过期时间,则过期时间会一起进行保存;

key_value_pairs部分完整的结构:

不带过期时间的键值对:

TYPE key value

TYPE : 表示对象类型,当服务器读入RDB文件的键值对时,程序会根据TYPE值来决定如何读入和解释value数据;

key/value : 保存键值对的键对象和值对象;键对象恒为字符串对象,而值对象会根据TYPE类型的不同而不同;

带有过期时间的键值对:

EXPIRETIME_MS ms TYPE key value

EXPIRETIME_MS : 常量,长度为1字节,程序读入该值,则知道接下来要读入的是一个以毫秒为单位的过期时间;

ms : 8字节长的带符号整数,记录一个以毫秒为单位的UNIX时间戳,该时间戳则是键值对的过期时间;

4. 分析RDB文件

可以使用od命令按照指定格式转存并打印RDB文件,常用的格式包括:-c 以ASCII编码的方式打印输入文件;

-x 以十六进制方式打印输入文件;

 

posted on 2022-12-27 13:29  VaeSSAQ  阅读(9)  评论(0)    收藏  举报