Redis数据结构篇

Redis是一个key-value存储系统,与Memcache相比,它的value支持更多数据类型。
Redis使用对象标识数据库中的键和值,每当在redis创建一个键值对时,会创建两个对象:即键对象、值对象。
Redis中的对象由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是:type属性、encoding属性、ptr属性。如下:

typedef struct redisObject{
    
    
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    指向底层实现数据结构的指针
    void *ptr;
}robj;

一、redis的数据类型

type属性记录了对象的类型,这个属性的值可以是以下常量中的其中之一:

类型常量 对象的名称
REDIS_STRING 字符串对象
REDIS_LIST 列表对象
REDIS_HASH 哈希对象
REDIS_SET 集合对象
REDIS_ZSET 有序集合对象

其中,Redis的键总是一个字符串对象,值可以是以上5种对象之一。
使用 TYPE key来查看某个数据库键的值对象的数据类型,如:

redis> set name "zs"
OK
redis> TYPE name
"string"

二、redis的编码实现

encoding属性记录了对象所使用的编码,也就是这个对象使用了什么数据结构作为对象的底层实现;ptr指针指向对象的底层实现数据结构。
redis每种数据类型的对象使用了不同的编码,下表列出了每种类型的对象可以使用的编码:

类型 编码 对象
REDIS_STRING REDIS_ENCODING_INT 使用整数值实现的字符串对象
REDIS_STRING REDIS_ENCODING_EMBSTR 使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRING REDIS_ENCODING_RAW 使用简单动态字符串实现的字符串对象
REDIS_LIST REDIS_ENCODING_ZIPLIST 使用压缩列表实现的列表对象
REDIS_LIST REDIS_ENCODING_LINKEDLIST 使用双端链表实现的列表对象
REDIS_HASH REDIS_ENCODING_ZIPLIST 使用压缩列表实现的哈希对象
REDIS_HASH REDIS_ENCODING_HT 使用字典实现的哈希对象
REDIS_SET REDIS_ENCODING_INTSET 使用整数集合实现的集合对象
REDIS_SET REDIS_ENCODING_HT 使用字典实现的集合对象
REDIS_ZSET REDIS_ENCODING_ZIPLIST 使用压缩列表实现的有序集合对象
REDIS_ZSET REDIS_ENCODING_SKIPLIST 使用跳表实现的有序集合对象

使用 OBJECT ENCODING key来查看某个数据库键的值对象的编码,如:

redis> set name "zs"
OK
redis> OBJECT ENCODING name
"embstr"

为特定类型的对象关联多种编码,极大提高了Redis的灵活性和效率,Redis可以根据不同的使用场景为一个对象设置不同的编码,从而优化对象在某个场景下的效率。接下来介绍每种类型在不同场景下使用编码的方式。

三、不同对象的编码实现和编码转换

1. 字符串对象

字符串对象的编码可以是int、raw、embstr。

  • int:字符串对象保存的是整数值,并且这个整数值可以用long类型表示;
  • raw:字符串对象保存的是字符串值,并且这个字符串值的长度大于32字节;
  • embstr:字符串对象保存的是字符串值,并且这个字符串值的长度小于等于32字节;
(1)int编码

int编码的字符串对象
示例:

redis> set number 10086
OK
redis> OBJECT ENCODING number
"int"
(2)raw编码

raw编码的字符串对象
示例:

redis> set story "Long,long ago there lived a king..."
OK
redis> STRLEN story
(integer) 37
redis> OBJECT ENCODING story
"raw"
(3)embtsr编码

embstr编码是用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过一次内存分配函数来分配一块连续空间,空间中依次包含redisObject结构和sdshdr结构。因此,embstr编码比raw编码分配和释放内存的效率高,并且能够更好地利用缓存带来的优势。
embstr编码的字符串对象
示例:

redis> set msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"
(4)编码转换

int和embstr编码的对象在条件满足的情况下,会转换为raw编码的字符串对象。
(a) int编码的字符串对象在执行了某些命令后,使得对象保存的不再是整数值,而是字符串值,编码将从int变为raw。如:

redis> set number 10086
OK
redis> OBJECT ENCODING number
"int"
redis> APPEND number “ is a good number”
(integer) 23
redis> OBJECT ENCODING number
"raw"

(b) embstr编码的字符串对象是只读的,当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换为raw,然后再执行修改命令,所以embstr编码的字符串对象在修改之后会变为raw编码。如:

redis> set msg "hello"
OK
redis> OBJECT ENCODING msg
"embstr"
redis> APPEND msg " world"
(integer) 11
redis> OBJECT ENCODING msg
"raw"

2. 列表对象

列表对象的编码可以是ziplist、linkedlist。

  • ziplist:同时满足:(1)列表对象保存的所有字符串元素的长度都小于64字节;(2)列表对象保存的元素数量小于512个;
  • linkedlist:以上2个条件有一个不满足时,使用linkedlist编码。
(1)ziplist编码

ziplist编码的列表对象
示例:

redis> RPUSH numbers 1 "three" 5
(integer) 3
redis> OBJECT ENCODING numbers
"ziplist"
(2)linkedlist编码

linkedlist编码的列表对象
stringObject的结构

示例:

redis> RPUSH numbers 1 "three" 5 .......
(integer) 513
redis> OBJECT ENCODING numbers
"linkedlist"
(3)编码转换

对于使用ziplist编码的列表对象来说,当使用ziplist编码的两个条件的任意一个不能满足时,对象的编码就会转换为linkedlist。

3. 哈希对象

哈希对象的编码可以是ziplist、hashtable。

  • ziplist:同时满足:(1)哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;(2)哈希对象保存的键值对数量不超过512个;
  • hashtable:以上2个条件有一个不满足时,使用hashtable编码。
(1)ziplist编码

当有新的键值对要加入哈希对象时,会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾。
ziplist编码的哈希对象
哈希对象的压缩列表底层实现

示例:

redis> HSET profile name "Tom"
(integer) 1
redis> HSET profile age 25
(integer) 1
redis> HSET profile career "Programmerr"
(integer) 1
redis> OBJECT ENCODING numbers
"ziplist"
(2)hashtable编码

hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存。
hashtable编码的哈希对象

示例:

redis> HSET  book "long_long_long_long_long_long...."
(integer) 1
redis> OBJECT ENCODING numbers
"hashtable"
(3)编码转换

对于使用ziplist编码的哈希对象来说,当使用ziplist编码的两个条件的任意一个不能满足时,对象的编码就会转换为hashtable。

4. 集合对象

集合对象的编码可以是intset、hashtable。

  • intset:同时满足:(1)集合对象保存的所有元素都是整数值;(2)集合对象保存的元素数量不超过512个;
  • hashtable:以上2个条件有一个不满足时,使用hashtable编码。
(1)intset编码

使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里。
intset编码的集合对象

示例:

redis> SADD numbers 1 3 5
(integer) 3
redis> OBJECT ENCODING numbers
"intset"
(2)hashtable编码

hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串,字典的值全部被设置为NULL。
hashtable编码的集合对象

示例:

redis> SADD fruits "cherry" "apple" "banana"
(integer) 3
redis> OBJECT ENCODING fruits
"hashtable"
(3)编码转换

对于使用intset编码的集合对象来说,当使用intset编码的两个条件的任意一个不能满足时,对象的编码就会转换为hashtable。

5. 有序集合对象

有序集合对象的编码可以是ziplist、skiplist。

  • ziplist:同时满足:(1)有序集合保存的元素数量小于128个;(2)有序集合保存的所有元素成员的长度都小于64字节;
  • skiplist:以上2个条件有一个不满足时,使用skiplist编码。
(1)ziplist编码

ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点保存,第一个节点保存元素的成员,第二个元素则保存元素的分值。压缩列表内的集合元素按分值从小到大排列,分值较小的元素被放在靠近表头的方向,分值较大的元素被放在靠近表尾的方向。
ziplist编码的有序集合对象
有序集合元素在压缩列表中按分值从小到大排列

示例:

redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
redis> OBJECT ENCODING price
"ziplist"
(2)skiplist编码

skiplist编码的有序集合对象使用zset作为底层实现,一个zset结构同时包含一个字典(dict)和一个跳跃表(zsl)。
zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表的object属性保存了元素的成员,跳跃表的score属性保存了元素的分值。通过跳跃表,可以对有序集合进行范围操作,比如:ZRANK、ZRANGE等命令就是基于跳跃表API来实现。
dict字典为有序集合创建了一个从成员到分值的映射,字典的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,字典的值保存了元素的分值。通过字典,程序可以以O(1) 复杂度查找给定成员的分值,比如:ZSCORE命令。
skiplist编码的有序集合对象
有序集合对象同时被保存在字典和跳跃表中

示例:

redis> ZCARD numbers
(integer) 128
redis> OBJECT ENCODING numbers
"ziplist"
redis> ZADD numbers 3.14 pi
(integer) 1
redis> ZCARD numbers
(integer) 129
redis> OBJECT ENCODING numbers
"skiplist"
(3)编码转换

对于使用ziplist编码的有序集合对象来说,当使用ziplist编码的两个条件的任意一个不能满足时,对象的编码就会转换为skiplist。

四、参考文献

[1]: 《Redis设计与实现》第一部分 数据结构与对象

猜你喜欢

转载自blog.csdn.net/zpy20120201/article/details/109275255