之前相关联的文章:
redis学习之一SDS
redis学习之二双端链表
redis学习之三字典
redis学习之四skiplist
redis学习之五ziplist
redis学习之六对象
redis学习之七字符串对象
redis学习之八列表对象
redis学习之九哈希对象
redis学习之十汇合对象
先再看一下redisObject的定义:
typedef struct redisObject{ //类型 unsigned type:4; //编码 unsigned encoding:4; //lru缓存淘汰机制信息 unsigned lru:LRU_BITS; //援用计数器 int refcount; //指向底层实现数据结构的指针 void *ptr;}robj;
有序汇合的编码能够是ziplist或skiplist,当同时满足上面两个条件时应用ziplist编码,不满足的话会应用skiplist编码:
- 保留的元素不超过128个。
- 保留的元素成员长度不超过64字节。
这两个值也是能够通过zset-max-ziplist-value配置进行调整的。
先来说下ziplist编码无关的状况:每个元素应用两个紧挨在一起的压缩列表来保留数据,第一个节点保留元素的成员(member),第二个节点保留的是分值(score),分值从小到大排列,也就是分值较小的排在表头后面,较大的排在凑近表尾一侧。
看下上面的命令:
redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry(integer) 3
存储示意图为:
能够看的进去元素是按分值递增排序的。上面来看下应用skiplist编码是如何存储的,先看下示意图:
skiplist编码的有序汇合对象应用zset构造作为底层实现,一个zset蕴含一个字典和一个跳表:
typedef struct zset{ zskipliset *zsl; dict *dict;}zset;
咱们分两局部别离说下dict与zsl,先看zsl:
zsl跳表按分值从小到大保留了所有汇合元素,跳表节点里的object保留了元素成员,跳表里的score属性保留的是元素的分值。
再来看看dict里的内容:
dict存的是元素成员到分值的映射关系:dict的健保留了元素的成员,dict的值则保留了元素分值,通过这个关系,能够用O(1)的复杂度找到给定的分值。
须要阐明下,只管zset同时应用dict与zskiplist两种数据结构保留元素,但这两种数据结构会通过指针来共享雷同元素的成员和分值,所以不会产生同一元素屡次存储,也就不会造成内存的节约。
最初,咱们来聊几个问题:
- 为什么有序汇合会同时应用dict与ziplist两种数据结构呢?
依据下面提到的两种数据结构的存储原理是很好答复的:
- 应用dict是为了利用hash算法,以O(1)的工夫复杂度查问成员的分值,这就是命令ZSCORE实现的根底。
- 应用skiplist让咱们能够以O(logN)的工夫复杂度进行依据分值查问成员、查问成员的排名操作,这就是命令ZRANK/ZRANGE实现的根底。
- 剖析下命令ZRANGE key start stop命令的工夫复杂度
咱们晓得这命令会返回有序汇合中指定区间内的成员,工夫复杂度为O(logN + M),其中 N 为有序集的基数,而 M 为后果集的基数。是这样的:通过skiplist能够以O(logN)的工夫复杂度定位到范畴start开始的元素,而后再以M的工夫复杂度定位到stop完结的元素。有个阐明:zrange工夫复杂度的阐明 - 剖析下ZRANK key member的工夫复杂度
从文档上看这个命令是返回有序集 key 中成员 member 的排名,工夫复杂度为O(logN),进行工夫复杂度的剖析咱们须要看下ziplist里跨度(redis学习之四skiplist)的含意:跨度是用于记录两个节点之间的间隔的,它是用来计算排位(rank)的,在查找某个节点过程中,将沿途拜访过的所有层的跨度累计起来,失去的后果就是指标结点在ziplist里的排位,计算排位其实就相当于依据分值进行元素的查找,在查找的过程中累加跨度的常量计算能够疏忽,这样整体工夫复杂度就是O(logN)了。
本文参考的有:
黄健宏的《Redis设计与实现》一书