大纲:简述Redis五种对象所使用的的底层数据结构
- 字符串对象
- 列表对象
- 哈希对象
- 集合对象
- 有序集合对象
阅读本文你将收获什么:
- 了解Redis五种对象的实现以及优点。
- 了解对象系统设计上的优点。
简述:
上文我们已经了解过了Redis底层的六种数据结构,然而这六种数据结构咱们并不能直接使用,而是基于这些数据结构创建了Redis的对象系统,每种对象都用到了至少一种前文所述的数据结构。
这种以对象来实现方式的好处在于,可以根据对象的类型来判断对象是否可以执行给定的命令。另一个好处在于,我们可以针对不同的使用场景,为对象设置不同的数据结构实现,优化使用效率。
一。对象的编码与类型
图1·Redis对象与编码
图一表示了Redis与其对象系统,也就是咱们常用的五大类型,以及五大类型底层所有的编码方式。
对象的编码:
编码常量 | 编码所对应的底层数据结构 |
---|---|
REDIS_ENCODING_INT | long类型的整数 |
REDIS_ENCODING_EMBSTR | embstr编码的简单动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典 |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSE | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
二.字符串对象
字符串对象的编码可以是int、raw、或者embstr。
-
如果一个字符串对象保存的是整数值,且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面,并且将字符串对象的编码设置为int。
图2·int编码的字符串对象 -
如果字符串对象保存的是一个字符串值,且长度大于39字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个值,并将对象的编码设置为raw。
图3·raw编码的字符串对象 -
如果保存的字符串值的长度小于39字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。
图4·embstr编码创建的内存块结构
使用embstr保存短字符串值的好处
- embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次。
- 释放embstr编码的字符串对象只需要调用一次内存释放函数,而释放raw编码的字符串对象需要调用两次。
- 因为embstr编码的字符串对象所有的数据都保存在一块连续的内存里,所以这种编码的字符串对象比raw编码的字符串对象能够更好的利用缓存带来的优势。
Redis没有为embstr编码的字符串对象编写任何相应的修改程序,所以embstr编码的字符串对象实际上是只读的,任何对其修改的命令,程序都会将其编码从embstr转换成raw,在执行修改。
三.列表对象
列表对象的编码可以是ziplist或者linkedlist。
- ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了一个列表元素。
图5·ziplist编码的number列表对象
- 另一方面,linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点(node)都保存了一个字符串对象,每个字符串对象都保存了一个列表元素。
图6·linkedlist编码的列表对象
字符串对象是Redis五种类型的对象中唯一一种会被其他四种对象嵌套的对象。
编码转换
当列表对象可以同时满足以下两个条件时,列表对象使用ziplist编码:
- 列表对象保存的所有字符串长度都小于64字节;
- 列表对象保存的元素数量小于512个。
四.哈希对象
哈希对象的编码可以是ziplist或者hashtable。
- ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表表尾,因此:
- 保存了同一键值对的两个接地那总是紧挨在一起,保存键的节点在前,保存值的节点在后;
- 先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象中的键值对会被放在压缩列表的表尾方向。
举例:
ziplist编码的哈希对象
图7·ziplist编码的哈希对象
压缩列表实现如下:
图8·哈希对象的压缩列表底层实现
- hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都是用一个字典键值对来保存:
- 字典的每个键都是一个字符串对象,对象中保存了键值对的键。
- 字典的每个值都是一个字符串对象,对象中保存了键值对的值。
图9·hashtable编码的哈希对象
编码转换
当哈希对象可以同时满足以下两个条件时,哈希对象使用ziplist编码:
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
- 哈希对象保存的键值对数量小于512个。
五.集合对象
集合对象的编码可以是intset或者hashtable。
- intset编码的集合对象使用整数集合作为底层实现,集合对象包含所有元素都被保存在整数集合里面。
图10·intset编码的集合对象
- 另一方面,hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值全部被设置为NULL。
图11·hashtable编码的集合对象
编码转换
当集合对象可以同时满足以下两个条件时,对象使用intset编码:
- 集合对象保存的所有元素都是整数值;
- 集合对象保存的元素数量不超过512个。
六.有序集合对象
有序集合的编码可以是ziplist或者skiplist。
- ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存分值。
压缩列表内的几何元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的位置,而分值较大的元素则被放置在靠近表尾的位置。
图12·有序集合的压缩列表
- zset结构中的zsl跳跃表按照分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作。
除此之外zset结构中的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,值则保存了元素的分值。通过字典,程序可以以O(1)的复杂度查找给定成员的分值。
图13·skiplist编码的有序集合对象
图14·有序集合元素同时被保存在字典和跳跃表中
编码转换
当有序集合对象可同时满足以下两个条件时,对象使用ziplist编码:
- 有序集合保存的元素数量小于128个;
- 有序集合保存的所有元素成员的长度都小于64字节。
总结
以上就是Redis的对象系统如何使用底层基本数据结构来针对不同的场景进行实现。同时,经过分析,可以发现,Redis的对象系统的类型与编码组合使用以达到多种适用于不同类型, 不同长度数据存储的巧妙设计。