欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《Redis设计与实现》阅读笔记5-RDB持久化

程序员文章站 2024-03-21 11:09:22
...

9 RDB(Redis DataBase)

9.1 RDB是什么

  • Redis是内存数据库,所以退出后需要某种文件将数据库信息保存到磁盘里面,而rdb文件就是保存的一种形式
  • 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
  • RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
  • fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程

9.2 rdb文件的创建与载入

9.2.1 rbd文件的创建

  • 有三种方式会触发dump.rdb文件的产生
  • 文件产生条件:
    • 1.默认产生方式
      《Redis设计与实现》阅读笔记5-RDB持久化
      默认为900s内修改过一次,或者300s内修改过10次,或者60秒内修改过10000次,会触发产生dump.rdb文件。
    • 2..其次另外一种触发方式,是在redis中输入sava或bgsave命令,也会直接产生rdb文件。rdb的创建工作实际上是由rdb.c/rdbSave函数完成,save和bgsave都会以不同的方式调用这个函数。
    • 3.执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
  • rbd文件:
    《Redis设计与实现》阅读笔记5-RDB持久化
    当产生条件被触发时,rdb会产生,产生位置可以查看配置文件
    《Redis设计与实现》阅读笔记5-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倍的膨胀性需要考虑