新闻资讯
Redis 的底层数据结构(对象)
下面我们就具体来看看 redis 中都有哪些对象,底层又对应哪些可供选择的数据结构。
一、Redis 对象结构定义
redis 为每个对象定义为如下数据结构:
typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr;
} robj;
type 记录的是当前的对象类型,有以下几种类型:
#define OBJ_STRING 0 /*字符串对象*/ #define OBJ_LIST 1 /*列表对象*/ #define OBJ_SET 2 /*集合对象*/ #define OBJ_ZSET 3 /*有序集合对象*/ #define OBJ_HASH 4 /*哈希对象*/
encoding 记录的是当前对象使用的哪种底层数据结构实现的,有以下类型可供选择:
#define OBJ_ENCODING_RAW 0 /* SDS 字符串 */ #define OBJ_ENCODING_INT 1 /* 整数 */ #define OBJ_ENCODING_HT 2 /* 字典结构 */ #define OBJ_ENCODING_ZIPMAP 3 /* 压缩map,已经废弃 */ #define OBJ_ENCODING_LINKEDLIST 4 /* LinkedList 双端链表,废弃了 */ #define OBJ_ENCODING_ZIPLIST 5 /* 压缩列表 */ #define OBJ_ENCODING_INTSET 6 /* 整数集合 */ #define OBJ_ENCODING_SKIPLIST 7 /* 跳跃表 */ #define OBJ_ENCODING_EMBSTR 8 /* 短字符串 */ #define OBJ_ENCODING_QUICKLIST 9 /* 压缩链表和双向链表组成的快速列表 */
8 和 9 我们遇到时在介绍,这里暂时不做介绍。
lru 记录的是上一个当前对象实例被访问的时间,它用作计算对象空转时长,空转时长过大的对象会被 redis 优先释放内存。
refcount 记录的是对象的引用计数,引用计数算法是很多编程语言中管理对象是否应该被销毁的依据,和它类似的典型的 Java 中可达性分析算法,都是用于计数当前对象是否依然被使用,以便释放内存。
ptr 指针指向的是实际实现当前对象的数据结构首地址。
以上就是 redisObject 数据结构的基本解释,下面我们看具体的对象分别会在什么情况下切换不同的底层实现。
二、字符串对象
字符串对象有三种 encoding 值,也就是只有这三种情况,redis 才会使用字符串对象存储数据。
- 字符串(raw):普通的字符串
- 整数(int):long 类型的整数值
- 短字符串(embstr ):短字符串
如果判定使用 raw 编码,那么 redis 的 ptr 指针将会指向一个 SDS 结构,如果确定使用 int 编码,那么会将 redisObject 中 ptr 类型由 void* 变成 long,继而分配 robj 内存。
当字符串的长度小于 39 个字节时,会采用 embstr 这种编码,embstr 其实也是使用 SDS 进行存储,区别于 raw 编码的是,后者会将 robj 和 ptr 指向的 SDS 分配在连续的内存块,唯一的好处是分配和释放内存都只需要一次操作即可完成,再一个是因为数据相邻,有可能一次加载 robj 的时候,CPU 将后面的 embstr 也加载进缓存,等到访问的时候就可以直接从缓存中访问。
但是,我们看一个例子:
hello 原本是以 int 编码存储的,但是我们执行 append 命令添加了字符串之后,它变成了 raw 编码。
这其实是 redis 的一种编码换换,当 hello 不再适合使用 int 编码继续存储的时候,会进行一个编码转换。
三、列表对象
列表对象有两种编码,压缩列表 ziplist 和 linkedlist。我们之前说过压缩列表的推荐应用场景,少量整数或字符串的时候可以用压缩列表来节省内存空间,而大数据量的节点则推荐使用普通的双端链表进行实现。
但是实际上,redis 的较新版本已经使用一种叫 quicklist 的快速列表整合 ziplist 和 linkedlist 作为列表对象的实现了。它将所有的节点分段拆分,每一份又使用压缩列表进行压缩,不同段之间使用双向指针连接。
四、集合对象
集合对象也有两种编码,整数集合 intset 和 字典 hashtable。默认情况下,当集合中有且仅有整型数据,且不超过 512 个,那么 redis 会使用整数集合 intset 进行集合存储,其余情况 redis 则构建字典进行集合数据存储。
顺便给大家复习下 intset 的无重复性、顺序性的特性,重复的元素是插入不进去的,因为插入之前会通过二分查找查找是否存在该元素,如果存在则拒绝插入操作。
当然了,如果集合中元素个数超过 512,那么 redis 就转而使用字典结构进行数据存储,具体实例就不再演示了。
五、有序集合对象
有序集合对象同样使用两种编码 ziplist 和 skiplist,可能你又见到压缩列表的身影了,足以见得,压缩列表是一个非常优秀的数据结构。
同样,当有序集合中包含少量元素的时候,redis 会优先使用压缩列表进行存储,反之选择跳跃表。
sadd 命令的标准语法是:
ZADD KEY_NAME SCORE1 VALUE1.. SCOREN VALUEN
每一个元素都会对应一个分值,skiplist 本身的实现就需要这个分值进行元素的存储排序,有的时候有序集合会使用压缩列表进行实现,那么也需要这个分值来有序的压缩元素,这也是压缩列表页可以实现有序集合的原因。
这里补充一下,虽然说 redis 的有序集合是跳表实现的,这句话不错,但有失偏驳。
typedef struct zset { dict *dict;
zskiplist *zsl;
} zset;
准确来说,redis 中的有序集合是由我们之前介绍过的字典加上跳表(组合起来就是zset)实现的,字典中保存的数据和分数 score 的映射关系,每次插入数据会从字典中查询,如果已经存在了,就不再插入,有序集合中是不允许重复数据。
六、哈希对象
哈希对象的编码可以是 ziplist 或者 hashtable,没什么特殊的,不再赘述。
以上,我们总结了 redis 中五大对象结构,以及他们可选的底层实现数据结构,相信你也理解的不错,这将非常有助于我们后面的学习。
回复列表