1,前言

有关RDB文件生成相关的可以看:RDB持久化 本文涉及到的各种对象:Redis对象与数据结构

2,RDB文件结构

一个完整的RDB文件的示意图如下:

redis rdb文件 redis rdb文件解析_键值对

【为了方便区分变量、数据、常量,全文中用全大写单词标示常量,用全小写单词标示变量和数据】

  • RDB文件开头是REDIS部分,这个部分长5个字节,保存着REDIS五个字符。通过这五个字符,程序可以在载入文件时,快速判断是否是RDB文件
  • db_version长度为4个字节,它的值是一个字符串表示的整数,记录了RDB文件的版本号,比如0006表示RDB文件的版本为第六版
  • databases部分包含着零个或者任意个数据库,以及数据库中的键值对数据
  • EOF常量的长度为一个字节,这个常量标示着RDB文件正文内容的结束
  • check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDIS、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载入时,会将载入数据计算得出的校验和与check_sum进行比较,以此判断是否出现出错或者损坏

示例:

redis rdb文件 redis rdb文件解析_redis_02

2.1,databases部分

一个RDB文件的databases部分可以保存任意多个非空数据库,假设有一个服务器,其0号与3号数据库非空,那么其RDB文件为:

redis rdb文件 redis rdb文件解析_数据库_03

而每个非空数据库在RDB中又是按以下结构进行保存的:

redis rdb文件 redis rdb文件解析_字符串_04

  • SELECTDB常量的长度是一个字节,当读入程序遇到这个值的时候,它知道接下来要读入的将是一个数据库号码
  • db_number保存着一个数据库号码,根据号码的大小不同,这个部分的长度可以是1字节、2字节或者5字节。当读入db_number后,服务器会调用SELECT命令,根据读入的数据库号进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中
  • key_value_pairs部分保存了数据库中的所有键值对数据,如果键值对带有过期时间,那么过期时间也会和键值对保存在一起

redis rdb文件 redis rdb文件解析_redis_05

2.2,key_value_pairs部分

RDB文件中的每个key_value_pairs部分都保存了一个或以上数量的键值对,如果键值对带有过期时间的话,那么键值对的过期时间也会被保存在内

如果不带有过期时间的话,结构会是这样的:

redis rdb文件 redis rdb文件解析_字符串_06

带有过期时间的话,结构会是这样的:

redis rdb文件 redis rdb文件解析_redis_07

各部分的意义如下:

  • TYPE:记录了value的类型,长度为一个字节,当服务器读入RDB文件中的键值对时,会根据value的值决定如何读入和解释value的数据,值可以是以下常量的其中一个:
  • 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
  • key:总是一个字符串对象,它的编码方式和REDIS_RDB_TYPE_STRING类型的value一样
  • value:根据TYPE类型的不同,以及保存内容长度的不同,保存的value的结构和长度也会有所不同
  • EXPIRETIME_MS:常量的长度为一个字节,它告诉读入程序,接下来要读入的将是一个以毫秒为单位的过期时间
  • ms:一个8字节长的带符号证书,记录着一个以毫秒为单位的UNIX时间戳,这个时间戳就是这个键值对的过期时间

例如:

redis rdb文件 redis rdb文件解析_数据库_08

2.3,value编码

RDB文件中的每个value部分都保存了一个值对象,每个值对象的类型都由与之对应的TYPE记录,根据类型不同,value的长度与结构也都不同

2.3.1,字符串对象

如果TYPE的值是REDIS_RDB_TYPE_STRING,那么value保存的就是一个字符串对象,字符串对象的编码可以为:

  • REDIS_ENCODING_INT
  • REDIS_ENCODING_RAW

REDIS_ENCODING_INT

如果字符串对象的编码为:REDIS_ENCODING_INT,那么说明保存的是长度不超过32位的整数,这种编码的对象将会按照下图的结构进行存储(假设字符串对象中保存的是用8位来保存的整数123):

redis rdb文件 redis rdb文件解析_数据库_09

REDIS_ENCODING_RAW

如果字符串编码为:REDIS_ENCODING_RAW,那么说明这个对象所保存的是一个字符串值,根据字符串长度的不同,有压缩和不压缩两种方法进行保存:

  • 如果字符串的长度小于等于20字节,字符串会被原样保存
  • 如果字符串的长度大于20字节,那么这个字符串会被压缩后再进行保存

【值得一提的是:只有当服务器打开了RDB文件压缩功能时才会出现上述的情况;否则RDB程序总会以无压缩的方式保存字符串值】

如果是以不压缩的方式进行保存的话:

redis rdb文件 redis rdb文件解析_数据库_10

  • string:保存了字符串本身
  • len:保存了字符串值的长度

如果是以压缩的方式进行保存的话,RDB程序会用以下的结构进行保存:

redis rdb文件 redis rdb文件解析_键值对_11

  • REDIS_RDB_ENC_LZF:表示字符串已经被LZF算法压缩过;读入程序碰到这个常量后,会根据之后的compressed_len、origin_len、compressed_string三部分,对字符串进行解析
  • compressed_len:字符串压缩过后的长度
  • origin_len:字符串原来的长度
  • compressed_string:被压缩后的字符串

2.3.2,列表对象

如果TYPE的值是REDIS_RDB_TYPE_LIST,那么value保存的就是一个REDS_ENCODING_LINKEDLIST编码的列表对象,RDB文件保存这种对象的结构如下:

redis rdb文件 redis rdb文件解析_java_12

list_length记录了列表的长度,代表列表保存了多少个项,读入程序可以根据这个知道应该读入多少个列表项。以一个包含三个元素的列表为例:

redis rdb文件 redis rdb文件解析_键值对_13

  • 第一个数字3是列表的长度,之后跟着的是三个列表项
  • 第一个列表项的长度为5,内容为字符串“hello”
  • 第二个列表项的长度为5,内容为字符串“world”
  • 第三个列表项的长度为1,内容为字符串“!”

2.3.3,集合对象

如果TYPE的值是REDIS_RDB_TYPE_SET,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB文件保存这种对象的结构如下:

redis rdb文件 redis rdb文件解析_redis_14

  • set_size:集合的大小,记录了集合保存多少个元素
  • elemX:集合项,因为每个集合元素都是字符串对象,所以程序会以处理字符串对象的方式来保存和读入集合元素

例如有一个包含四个元素的集合:

redis rdb文件 redis rdb文件解析_redis_15

  • 第一个数字4,表示有四个元素
  • 第一个元素的长度为5,存储的内容是:”apple“
  • 第二个元素的长度为6,存储的内容是:”banana“
  • 第三个元素的长度为3,存储的内容是:”cat“
  • 第四个元素的长度为3,存储的内容是:”dog“

2.3.4,有序集合对象

如果TYPE的值为REDIS_RDB_TYPE_ZSET,那么value保存的就是一个REDIS_ENCODING_SKIPLIST编码的有序集合对象,RDB文件保存这种对象的结构如下所示:

redis rdb文件 redis rdb文件解析_数据库_16

  • sorted_set_size:有序集合的大小,表示这个有序集合保存了多少元素
  • elementX:有序集合中的元素,每个元素又分为成员(member)与分值(score),成员是一个字符串对象,分值则是一个double类型的浮点数。程序在存储分值时,会将其转为字符串对象,再进行保存。

再细分下,能得到下图:

redis rdb文件 redis rdb文件解析_redis_17

例如有一个带有两个元素的有序集合:

redis rdb文件 redis rdb文件解析_字符串_18

  • 第一个数字2表示有序集合中保存了两个元素
  • 第一个集合项的成员长度为2,内容为:”pi“,分值被转换成字符串之后变成了长度为4的字符串:”3.14“
  • 第二个集合项的成员长度为1,内容为:”e“,分值被转换成字符串之后变成了长度为3的字符串:”2.7“

2.3.5,哈希对象

如果TYPE的值为REDIS_RDB_TYPE_HASH,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB保存这种对象的结构如下图所示:

redis rdb文件 redis rdb文件解析_键值对_19

  • hash_size:哈希表的大小,表示哈希表保存了多少键值对
  • key_value_pair X:代表哈希表中的键值对,键值对中的键和值都是字符串对象,所以程序会以处理字符串对象的方式来保存和读入键值对

结构中的每个键值对都以键紧挨着值的方式排列在一起,所以可以细分为:

redis rdb文件 redis rdb文件解析_java_20

假设有一个保存着两个键值对的哈希表:

redis rdb文件 redis rdb文件解析_redis_21

  • 第一个数字2,记录了哈希表的键值对数量
  • 第一个键值对的键长度为1,保存的字符串内容为:“a”;值是长度为5的字符串“apple”
  • 第二个键值对的键长度为1,保存的字符串内容为:“b”;值是长度为6的字符串“banana”

2.3.6,INSET编码的集合

如果TYPE的值为REDIS_RDB_TYPE_SET_INTSET,那么value保存的就是一个整数集合对象,RDB文件保存这种对象的方法是,先将整数集合转换为字符串对象,然后将这个字符串对象保存到RDB文件中

在读入RDB文件的过程中,如果遇到由整数集合转换而得来的字符串对象,那么程序会根据TYPE值,先读入字符串对象,再将其转换为原来的整数集合对象

2.3.7,ZIPLIST编码的列表、哈希表或者有序集合

如果TYPE的值是以下几种之一:

  • REDIS_RDB_TYPE_LIST_ZIPLIST
  • REDIS_RDB_TYPE_HASH_ZIPLIST
  • REDIS_RDB_TYPE_ZSET_ZIPLIST

那么value保存的就是一个压缩列表,RDB保存着类对象的方法是:

  1. 将压缩列表转换为一个字符串对象
  2. 将转换所得的字符串对象保存到RDB文件中

在读入RDB文件时,如果遇到这种情况产生的字符串对象时,程序会根据TYPE值的指示,执行以下操作:

  • 读入字符串对象,将其转换为原来的压缩列表对象
  • 根据TYPE的值,设置压缩列表对象的类型:
  • 如果TYPE的值为:REDIS_RDB_TYPE_LIST_ZIPLIST,那么压缩列表对象的类型为列表
  • 如果TYPE的值为:REDIS_RDB_TYPE_HASH_ZIPLIST,那么压缩列表对象的类型为哈希表
  • 如果TYPE的值为:REDIS_RDB_TYPE_ZSET_ZIPLIST,那么压缩列表对象的类型为有序集合