《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文件,但里面是空的,无意义
  • 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倍的膨胀性需要考虑

猜你喜欢

转载自blog.csdn.net/maniacxx/article/details/82560227