文章目录
RDB文件保存数据库状态
服务器中的非空数据库及其键值对,统称为数据库状态。Redis的RDB持久化,将数据库状态保存到磁盘,避免内存数据掉电易失。
RDB持久化既可以手动执行,也可以通过配置定期执行。它会将某个时间点的数据库状态保存到一个RDB文件(压缩的二进制文件)中,以便后期方便借此还原数据库状态。
1. RDB文件的创建和载入
RDB文件的创建由rdbSave函数负责,SAVE命令和BGSAVE命令会以不同的形式调用该函数:
- SAVE命令会阻塞进程,直到RDB文件创建完毕。在此期间,服务器不会响应任何命令请求
- BGSAVE命令会fork一个子进程出来,由子进程负责生成RDB文件,父进程依旧继续处理命令请求
和RDB创建不同的是,RDB文件的载入并没有专门的函数、命令,只要Redis服务器启动时检测到RDB文件存在,就会自动将其载入(开启AOF,优先使用AOF;关闭AOF,使用RDB)。在RDB载入期间,服务器会一直处于阻塞状态,直至RDB载入完毕!
虽说在BGSAVE命令执行期间,服务器主进程会正常响应命令请求,但是对3个命令除外:
- SAVE命令:BGSAVE命令执行期间,服务器会拒绝SAVE命令。服务器禁止SAVE/BGSAVE命令同时执行,是为了避免父进程、子进程同时执行rdbSave函数(防止产生竞争条件)
- BGSAVE命令:BGSAVE命令执行期间,服务器会拒绝BGSAVE命令。同时执行两个BGSAVE命令也会产生竞争条件
- BGREWRITEAOF命令:
- ①如果BGSAVE命令正在执行,BGREWRITEAOF命令会被排到BGSAVE命令执行完毕后执行
- ②如果BGREWRITEAOF命令正在执行,服务器会拒绝BGSAVE命令(都会fork子进程执行大量磁盘写入操作,性能考虑)
2.间隔执行BGSAVE命令
SAVE命令会阻塞服务器主进程,BGSAVE命令不会阻塞主进程(fork子进程执行RDB生成),所以Redis允许通过配置,让服务器间歇性的执行BGSAVE命令。
服务器提供以下配置:
# 服务器在X秒内,对数据库至少修改了Y次,就会触发BGSAVE命令的执行
save 900 1
save 300 10
save 60 10000
2.1 saveparam数组
服务器会根据上述save配置的条件,将其设置到服务器状态redisServer的saveparam数组中,数组的每个元素都是一个saveparam结构,包含秒数和修改次数:
struct saveparam *saveparams;
// 服务器的保存条件(BGSAVE 自动执行的条件)
struct saveparam {
// 多少秒之内
time_t seconds;
// 发生多少次修改
int changes;
};
2.2 dirty计数器和lastsave属性
- dirty计数器记录了上一次成功执行SAVE或BGSAVE命令后,服务器对服务器状态进行了多少次的修改(增删改)
- lastsave记录了上一次成功执行SAVE或BGSAVE命令的时间戳
2.3周期性检查save配置是否满足
服务器周期性操作函数serverCron(维护正在运行的服务器,职责之一就是检查save是否满足)默认每隔100ms就会执行一次:遍历saveparam数组中的所有元素,只要任意一条设置满足(次数、时间都符合),就会触发BGSAVE命令的执行。
BGSAVE命令执行完毕后,dirty计数器会被重置为0,lastsave保存的时间戳会被更新!
3.RDB文件结构
- REDIS:占5个字节的5个字符,可在服务器载入RDB时快速检查是否为RDB文件
- db_version:4字节的字符串表示的整数,表示RDB文件的版本号
- databases:零个或任意个数据库,及其内部的键值对数据
- EOF:占1字节,表示RDB文件正文部分已经结束
- check_sum:占8字节的无符号整数,对以上4部分计算出的校验和。载入RDB文件时程序会计算校验和,并与check_sum内的校验和比对,检查RDB文件是否损坏
假设0号、3号数据库非空
非空数据库的结构如下:
- SELECTDB:1字节的常量,表示下一个为数据库号码
- db_number:数据库号码(号码不同,1/2/5字节),程序读到此处,会调用SELECT命令,切换数据库
- key_value_pairs:保存了所有的键值对数据(过期键值对数据也在此处)
对于保存键值对数据的key_value_pairs部分结构: - EXPIRETIME_MS:占1字节,告诉程序接下来要读的是以ms为单位的过期时间
- ms:8字节,以ms为单位的过期时间
- TYPE:value的类型,占1字节
- key:字符串对象
- value:任意类型的Redis对象
TYPE记录了value的类型,程序会根据TYPE的值解析value:
① TYPE值为REDIS_RDB_TYPE_STRING,保存的是字符串对象
字符串对象的编码可以是:
- REDIS_ENCODING_INT(值为REDIS_RDB_ENC_INT8、REDIS_RDB_ENC_INT16、REDIS_RDB_ENC_INT32,表示RDB文件使用8/16/32位来保存整数)
- REDIS_ENCODING_RAW:保存的字符串(长度>20字节会被LZF算法压缩)
未压缩的字符串会以如下结构保存:
经过LZF压缩的字符串以以下结构保存(第1个是LZF标记,根据后3个解压):
② TYPE值为REDIS_RDB_TYPE_LIST,保存的是列表对象
列表长度 + 列表项
③ TYPE值为REDIS_RDB_TYPE_SET,保存的是集合对象
④ TYPE值为REDIS_RDB_TYPE_HASH,保存的是哈希表对象
⑤ TYPE值为REDIS_RDB_TYPE_ZSET,保存的是有序集合对象
⑥ TYPE值为REDIS_RDB_TYPE_SET_INTSET,保存的是整数集合对象
保存该对象的方式:先将整数集合转换为字符串对象,然后再保存到RDB文件中。
⑦ TYPE值为REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST、REDIS_RDB_TYPE_ZSET_ZIPLIST,保存的是压缩列表对象
将压缩列表对象转换为 字符串对象后,存储到RDB文件中。读RDB时会先将其转换为压缩列表后,再根据TYPE的值,转换为列表、哈希表、有序集合