所有分类
  • 所有分类
  • 未分类

Redis原理–数据类型的底层结构

简介

本文介绍Redis的数据类型的底层结构(内部编码)。包括:字符串、哈希、列表、集合、有序集合。

此内容也是Java后端面试常问的问题。

字符串

字符串类型的内部编码有3种:

  1. int: 8个字节的长整型(存放数字)。
  2. embstr: 小于等于39个字节的字符串(存放字符串)。
  3. raw: 大于39个字节的字符串(存放字符串)。

Redis会根据当前值的类型和长度决定使用哪种内部编码实现。

列表

列表类型的内部编码有两种。

1. ziplist(压缩列表)

当列表的元素个数小于list-max-ziplist-entries配置(默认512个) ,同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节) ,Redis会选用ziplist来作为列表的内部实现来减少内存的使用。

2. linkedlist(链表)

当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现,因为此时ziplist的读写效率会下降。

哈希

哈希类型的内部编码有两种:

1. ziplist(压缩列表)

当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个) 、同时所有值都小于hash-max-ziplist-value配置(默认64字节) 时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。

2. hashtable(哈希表)

当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。

集合

集合类型的内部编码有两种:

1. intset(整数集合)

当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个) 时, Redis会选用intset来作为集合的内部实现,从而减少内存的使用。

2. hashtable(哈希表)

当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。

有序集合

有序集合类型的内部编码有两种:

1. ziplist(压缩列表)

当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个) ,同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节) 时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。

2. skiplist(跳跃表)

当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

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

typedef struct zset {
	zskiplist *zsl;
	dict *dict;
}zset;

zsl跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表节点的score属性则保存了元素的分值。通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等命令就是基于跳跃表API来实现的。 

dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过这个字典,程序可以用0(1)复杂度査找给定成员的分值,ZSCORE命令就是根据这一特性实现的,而很多其他有序集合命令都在实现的内部用到了这一特性。

虽然zset结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构都会通过指针来共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会因此而浪费额外的内存。

为什么有序集合同时使用跳跃表和字典来实现?

有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现,但无论单独使用字典还是跳跃表,在性能上对比起同时使用字典和跳跃表都会有所降低。

举个例子,如果我们只使用字典来实现有序集合,那么虽然以0(1)复杂度查找成员的分值这一特性会被保留,但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作—比如ZRANK、ZRANGE等命令时,程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少O(NlogN)时间复杂度,以及额外的O(N)内存空间(因为要创建一个数组来保存排序后的元素)。

另一方面,如果我们只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的复杂度将从O(1)上升为O(logN)。因为以上原因,为了让有序集合的查找和范围型操作都尽可能快地执行,Redis选择了同时使用字典和跳跃表两种数据结构来实现有序集合。 ​

3

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录