《Redis设计与实现》阅读笔记5-RDB持久化
9 RDB(Redis DataBase)
9.1 RDB是什么
- Redis是内存数据库,所以退出后需要某种文件将数据库信息保存到磁盘里面,而rdb文件就是保存的一种形式
- 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
- RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
- fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
9.2 rdb文件的创建与载入
9.2.1 rbd文件的创建
- 有三种方式会触发dump.rdb文件的产生
- 文件产生条件:
- 1.默认产生方式
默认为900s内修改过一次,或者300s内修改过10次,或者60秒内修改过10000次,会触发产生dump.rdb文件。 - 2..其次另外一种触发方式,是在redis中输入sava或bgsave命令,也会直接产生rdb文件。rdb的创建工作实际上是由rdb.c/rdbSave函数完成,save和bgsave都会以不同的方式调用这个函数。
- 3.执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
- 1.默认产生方式
- rbd文件:
当产生条件被触发时,rdb会产生,产生位置可以查看配置文件
默认产生位置为./
表示产生位置为你的工作路径下
9.2.2 RDB文件与AOF文件的使用判断
- 如果服务器开启了AOF持久化功能,你们服务器会优先使用AOF文件来还原数据库状态,因为AOF文件的更新频率通常比RDB文件更新频率快
- 只有在AOF文件处于关闭状态,启动时才会使用RDB文件
- RDB文件在载入时服务器一直处于阻塞的状态,直到载入工作的完成
9.2.3 执行SAVE/BGSAVE命令时服务器的状态
持久化的方式不同:
- 第一种是由SAVE命令触发,这个命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器阻塞期间,服务器不接受任何命令请求
- 第二种是由于BGSAVE命令,或者默认产生方式触发,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,服务器进程继续处理命令请求。
9.2.4 SAVE/BGSAVE/BGREWRITEAOF命令的相互制约关系
- BGSAVE执行期间,SAVE命令会被拒绝,避免父进程与子进程同时执行两个rdbSave函数的调用,防止竞争条件
- BGSAVE执行期间,新的BGSAVE也会被拒绝,也是防止竞争
- BGSAVE执行期间,BGREWRITEAOF命令会被延迟到BGSAVE执行完以后再执行
- BGREWRITEAOF执行期间,BGSAVE命令会被拒绝
9.3 自动生成RDB文件的触发
前面提到的RDB文件生成方式中有一种默认触发生成的方式。
9.3.1 设置保存条件
默认触发的条件保存在redisServer数据结构中
struct redisServer{
//...
//记录保存条件的数组
struct saveparam *saveparams;
//...
};
saveparams属性是一个数组,数组中每个元素都是一个saveparam结构,每个该结构保存一组自动生成的条件
struct savparam{
//秒数
time_t seconds;
//修改数
int changes;
};
例如保存条件为默认条件900s内修改过一次,或者300s内修改过10次,或者60秒内修改过10000次,会触发产生dump.rdb文件。那么数组中的三个元素分别为900,1;300,10;60,10000。
9.3.2 dirty计数器和lastsave属性
在redisServer结构中有一个dirty属性与有关lastsave属性
struct redisServer{
//...
//修改计数器
long long dirty;
//上一次执行保存操作的时间
time_t lastsave;
//...
};
- dirty计数器记录距离上一次成功执行save或者bgsave操作命令之后,对数据库的修改次数(命令写入,删除,更新等操作),每次执行save或者bgsave操作命令之后,这个属性会被清零,后数据库每修改一次,这个属性自增1
- lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行save或者bgsave操作命令的时间。
9.3.2 检查条件是否满足
Redis服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,它会检查dirty属性和lastsave属性是否满足了saveparams数组中其中一个条件,具体操作就是遍历saveparams数组,逐个比较dirty属性和lastsave属性,如果满足就开始执行BGSAVE命令
9.4 RDB文件的结构
REDIS | db_version | databases | EOF | check_sum |
---|
- 开头文件最开头的是REDIS部分,这个部分的长度为5字节,保存着“REDIS”五个字符。作用是加载时确认它是RDB文件
- 在RDB文件中保存的是二进制数据,而不是C字符串,所以“REDIS”符号代表的是‘R’,’E’,’D’,’I’,’S’,而不是以‘/0’结尾的C字符串。
- db_version长度为4字节,它的值是一个字符串表示的整数,记录redis的版本号
- databases部分包含零个或任意多个数据库,以及各个数据库中的键值对数据
- 如果服务器的数据库状态为空,那么databases也为空。
- 如果服务器的数据库状态为非空(有一个数据库不为空),那么这个地方也非空
- EOF常量为1字节,标志RDB文件正文的结束
- check_sum是一个8字节长的无符号整数
9.4.1 databases部分
datebases部分可以保存任意多个非空数据库,如下示例保存0号数据库和3号数据库的数据
REDIS | db_version | database 0 | database 3 | EOF | check_sum |
---|
每个database在结构中由以下几部分组成
SELECTDB | db_number | key_value_pairs |
---|
- SELECTDB常量的长度为1字节,当读到这个常量的时候,就知道接下来会读到一个数据库号码
- db_number保存一个数据库号码,根据号码的大小不同,这个部分的长度可以是1字节,2字节或者5字节
- key_value_pairs部分保存数据库中所有的键值对,其长度与键值对的数量,类型,内容以及是否有过期时间而定
示例:
REDIS | db_version | SELECTDB | 0 | pairs | SELECTDB | 3 | pairs | EOF | check_sum |
---|
9.4.2 key_value_pairs部分
RDB文件中的每个key_value_pairs部分都保存了一个或以上数量的键值对,如果带过期时间的话,那么键值对的过期时间也会被保存
1.不带过期时间的键值对在RDB文件中由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的值类型一样
- value根据type类型的不同,保存的长度和类型也不同
2.带有过期时间的键值对的结构除了type,key,value三部分,还有EXPIRETIME_MS和ms两个部分。
- EXPIRETIME_MS常量的长度为1字节,则表示提示程序,接下来会读到一个毫秒数
- ms是一个8字节长的带符号整数,记录一个毫秒的UNIX时间戳,这个时间戳为键值对的过期时间
9.4.3 value部分
RDB文件的value部分保存一个值对象,每个value的类型由TYPE决定,根据了下的不同,value的长度与结构均不同。
1.字符串对象
若TYPE类型为REDIS_RDB_TYPE_STRING,那么value保存的就是一个字符串对象,编码为REDIS_ENCODING_INT(32位整数)或者REDIS_ENCODING_RAW(字符串值)。
- REDIS_ENCODING_INT又分为REDIS_ENCODING_INT8、REDIS_ENCODING_INT16、REDIS_ENCODING_INT32三个常量之一,分别代表RDB文件使用8位,16位,32位的常量之一。
- 如果RDB文件关闭压缩模式,那么使用REDIS_ENCODING_RAW的字符串都会被原样保存
- 如果RDB开启压缩模式
- 使用REDIS_ENCODING_RAW的字符串长度小于等于20字节,那么这个字符串会被直接原样保存
- 使用REDIS_ENCODING_RAW的字符串长度大于20字节,那么这个字符串会被压缩之后再保存
未被压缩字符串的保存方式
len | string |
---|
- len为字符串长度
- string为字符串本身
5 | “hello” |
---|
.
压缩字符串保存方式
REDIS_RDB_ENC_LZF | compressed_len | origin_len | compressed_string |
---|
- REDIS_RDB_ENC_LZF表示字符串已经被使用LZF算法压缩过,程序读到这个字符串以后,会根据后面的compressed_len、origin_len、compressed_string三部分对字符串解压缩
- compressed_len是字符串压缩以后的长度
- origin_len未被压缩的字符串长度
- compressed_string是被压缩的字符串
REDIS_RDB_ENC_LZF | 6 | 21 | “?aa???” |
---|
示例中的’?’表示无法用字符串打印的字符
2.列表对象
若TYPE值为REDIS_RDB_TYPE_LIST,那么value保存的就是一个REDIS_ENCODING_LINKEDLIST编码的列表对象,RDB文件保存这种对象结构如下
list_length | item1 | item2 | … | itemN |
---|
- list_length记录了列表的长度,他记录列表保存了多少项
- item开头的项为一个字符串对象,采用处理字符串的方式来存放
3 | 5 | “hello” | 5 | “world” | 1 | “!” |
---|
3.集合对象
如果TYPE的值为REDIS_RDB_TYPE_SET,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB中保存的结构如下
set_size | elem1 | elem2 | … | elemN |
---|
- set_size表示集合的大小,记录集合保存了多少个元素
- elem为里面的元素,每个元素为一个字符串对象
3 | 5 | “hello” | 5 | “world” | 1 | “!” |
---|
4.哈希表对象
如果TYPE的值为REDIS_RDB_TYPE_HASH,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB中保存的结构如下
hash_size | key_value_pair1 | key_value_pair2 | … | key_value_pairN |
---|
- hash_size记录哈希表的大小,为键值对的长度
- 每个key_value_pair为一个键值对,一个字符串key和一个字符串value排列在一起
hash_size | key1 | value1 | key2 | value2 | … | keyN | valueN |
---|
每个key和value都是一个字符串对象
2 | 1 | “a” | 5 | “apple” | 1 | “b” | 6 | “banana” |
---|
.
5 有序集合对象
如果TYPE的值为REDIS_RDB_TYPE_ZSET,那么value保存的就是一个REDIS_ENCODING_SKIPLIST编码的集合对象,RDB中保存的结构如下
sort_set_size | element1 | element2 | … | elementN |
---|
- sort_set_size记录有序集合中元素的个数
- element开头的都是储存的元素,每个element都为一个member和一个score组成
sort_set_size | member1 | score1 | member2 | score2 | … | memberN | scoreN |
---|
member记录元素内容,是一个字符串;score记录元素的分数,是一个double类型的浮点数。
2 | 2 | “pi” | 4 | 4 | “3.14” | 1 | “e” | 3 | “2.7” |
---|
.
6 INTSET编码的集合
如果TYPE的值为REDIS_RDB_TYPE_INTSET,那么value保存的就是一个整数集合对象,RDB中保存的方法是将整数转为字符串,然后将这些字符串对象保存到RDB文件中,Redis碰到整数转换成的字符串对象,会根据TYPE的提示,先读入字符串对象,再转换为整数集合对象
7 ZIPLIST编码的列表、哈希表、有序集合
如果TYPE的值为REDIS_RDB_TYPE_LIST_ZIPLIST、REDIS_RDB_TYPE_HASH_ZIPLIST或REDIS_RDB_TYPE_ZSET_ZIPLIST,那么value保存的就是一个压缩列表对象,RDB中保存的方法是将压缩列表的项转为字符串,然后将这些字符串对象保存到RDB文件中,Redis碰到压缩列表转换成的字符串对象,会根据TYPE的提示,先读入字符串对象,再转换为对应的对象
9.5 rdb文件的修复
有时候rdb文件存在错误的输入,可能是出现意外,未完全写入产生的,也可能是人为造成,这个时候我们可以打开文件自己进行比对修改,也可以使用redis提供的修复工具
redis-check-rdb --fix *.rdb
9.6 rdb的优劣
-
优点:
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
-
缺点
- 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改
- fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
推荐阅读
-
(六)、Redis的AOF持久化---Redis设计与实现读书笔记
-
《Redis设计与实现》阅读笔记5-RDB持久化
-
JAVAEE——宜立方商城06:Redis安装、数据类型和持久化方案、Redis集群分析与搭建、实现缓存和同步
-
《Redis设计与实现》[第一部分]数据结构与对象-C源码阅读(二)
-
《Redis设计与实现》读书笔记
-
redis设计与实现读书笔记——集群
-
redis设计与实现读书笔记——发布与订阅
-
Redis设计与实现读书笔记
-
JAVAEE——宜立方商城06:Redis安装、数据类型和持久化方案、Redis集群分析与搭建、实现缓存和同步
-
redis设计与实现读书笔记——底层数据结构