Redis五大数据类型的实现原理

Redis五大数据类型的实现原理


0. 写在前面

参考文章

博客园 Redis详解

对象的类型和编码

Redis中的每个对象都是由 redisObject 结构来表示:

typedef struct redisObject{
     //类型
     unsigned type:4;
     //编码
     unsigned encoding:4;
     //指向底层数据结构的指针
     void *ptr;
     //引用计数
     int refcount;
     //记录最后一次被程序访问的时间
     unsigned lru:22;
 
}robj
127.0.0.1:6379> set k1 ljj
OK
127.0.0.1:6379> type k1
string
127.0.0.1:6379> object encoding k1
"embstr"
127.0.0.1:6379> 

type属性

对象的type属性记录了对象的类型,这个类型就是前面讲的五大数据类型:

encoding 属性和 *prt 指针

对象的 prt 指针指向对象底层的数据结构,而数据结构由 encoding 属性来决定。

1. 字符串对象

1.1 编码

127.0.0.1:6379> set k1 1
OK
127.0.0.1:6379> object encoding k1
"int"
127.0.0.1:6379> set k2 aaaaaaabbbbbb
OK
127.0.0.1:6379> object encoding k2
"embstr"
127.0.0.1:6379> set k3 aaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccc
OK
127.0.0.1:6379> object encoding k3
"raw"
127.0.0.1:6379> 

字符串对象的编码可以是int,raw或者embstr。
  1、int 编码:保存的是可以用 long 类型表示的整数值。
  2、raw 编码:保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
  3、embstr 编码:保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。

embstr编码是专门用来保存短字符串的一种优化编码;相比于raw,embstr的redisObject和sds内存是连续的,好处是创建时少分配一次空间,删除时少释放一次空间坏处是embstr是只读的,数据如果变更,需要转化为raw

1.2 编码的转换

当 int 编码保存的值不再是整数,或大小超过了long的范围时,自动转化为raw;
对embstr对象进行修改时,都会先转化为raw再进行修改,无论是否达到了44个字节;

2. 列表对象

list 列表,它是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),它的底层实际上是个链表结构。

2.1 编码

列表对象的编码可以是 ziplist(压缩列表) 和 linkedlist(双端链表)

rpush numbers 1 "three" 5

2.2 编码转换

当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
  1、列表保存元素个数小于512个
  2、每个元素长度小于64字节
不能满足这两个条件的时候使用 linkedlist 编码。

2.3 优化后的编码:quicklist (redis3.2版本)

127.0.0.1:6379> lpush my_list 1 2 3 4 5 6 
(integer) 6
127.0.0.1:6379> object encoding my_list
"quicklist"

quickList 是 zipList 和 linkedList 的混合体,它将 linkedList 按段切分,每一段使用 zipList 来紧凑存储,多个 zipList 之间使用双向指针串接起来。

3. 哈希对象

3.1 编码

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

127.0.0.1:6379> hmset profile name ljj age 25 career Programmer
OK
127.0.0.1:6379> object encoding user
"ziplist"
127.0.0.1:6379> hset user desc aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
(integer) 1
127.0.0.1:6379> object encoding user
"hashtable"
127.0.0.1:6379>

3.2 编码转换

和上面列表对象使用 ziplist 编码一样,当同时满足下面两个条件时,使用ziplist(压缩列表)编码:
  1、列表保存元素个数小于512个
  2、每个元素长度小于64字节
不能满足这两个条件的时候使用 hashtable 编码。第一个条件可以通过配置文件中的 set-max-intset-entries 进行修改。

4. 集合对象

集合对象 set 是 string 类型的无序集合。注意集合和列表的区别:集合中的元素是无序的,因此不能通过索引来操作元素;集合中的元素不能有重复。

4.1 编码

127.0.0.1:6379> sadd num 123
(integer) 1
127.0.0.1:6379> object encoding num
"intset"
127.0.0.1:6379> sadd fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> object encoding fruits
"hashtable"
127.0.0.1:6379> 

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

4.2 编码转化

当集合同时满足以下两个条件时,使用 intset 编码:
  1、集合对象中所有元素都是整数
  2、集合对象所有元素数量不超过512
不能满足这两个条件的就使用 hashtable 编码。第二个条件可以通过配置文件的 set-max-intset-entries 进行配置。

5. 有序集合对象

和上面的集合对象相比,有序集合对象是有序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据。

5.1 编码

127.0.0.1:6379> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> object encoding price
"ziplist"
127.0.0.1:6379> zadd price 10 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccc
(integer) 1
127.0.0.1:6379> object encoding price
"skiplist"

skiplist 编码的有序集合对象使用 zet 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:

typedef struct zset{
     //跳跃表
     zskiplist *zsl;
     //字典
     dict *dice;
} zset;
字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

这两种数据结构会通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,造成内存的浪费。

说明:其实有序集合单独使用字典或跳跃表其中一种数据结构都可以实现,但是这里使用两种数据结构组合起来,原因是假如我们单独使用 字典,虽然能以 O(1) 的时间复杂度查找成员的分值,但是因为字典是以无序的方式来保存集合元素,所以每次进行范围操作的时候都要进行排序;假如我们单独使用跳跃表来实现,虽然能执行范围操作,但是查找操作有 O(1)的复杂度变为了O(logN)。因此Redis使用了两种数据结构来共同实现有序集合。

5.2 编码转换

当有序集合对象同时满足以下两个条件时,对象使用 ziplist 编码:
  1、保存的元素数量小于128;
  2、保存的所有元素长度都小于64字节。
不能满足上面两个条件的使用 skiplist 编码。以上两个条件也可以通过Redis配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容