Zset案例
先看一个最简单的zset案例
alpha:3>zadd key 1 tom # 添加key
"1"
alpha:3>zadd key 2 yorick
"1"
alpha:3>zadd key 3 yorick # 这里事实上是 将yorick 的score 由2改为了3
"0"
alpha:3>zrange key 0 10 withscores #查询指定区间内的 成员,并连key 一起返回
1) "tom"
2) "1"
3) "yorick"
4) "3"
alpha:3>zrank key yorick #查询yorick 的索引
"1"
alpha:3>zscore key yorick #查询yoirck 的分值
"3"
Zset 数据结构
zset的数据结构是 map 和 跳跃列表
从源码redis 3.0中看到定义 zset 结构如下:
/*
* 有序集合
*/
typedef struct zset {
// 字典,键为成员,值为分值
// 用于支持 O(1) 复杂度的按成员取分值操作
dict *dict;
// 跳跃表,按分值排序成员
// 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
// 以及范围操作
zskiplist *zsl;
} zset;
跳跃列表
跳跃列表类似下面这样:
1606036696502.png)]
那为什么要用这样的数据结构呢?答案是因为快,那为什么快呢?我们来看下面这个图:
假设我们要查找 19 。先从最上层的跳跃区间大的层開始查找。从头结点開始。首先和23进行比較,小于23,(此时查找指针在图中“1”位置处)。查找指针到下一层继续查找。
然后和9进行推断,大于9,查找指针再往前走一步和23比較,小于23。(此时查找指针在图中“2”位置处) 此时这个值肯定在9结点和23结点之间。查找指针到下一层继续查找。
然后和13进行推断。大于13,查找指针再往前走一步和23比較,小于23,(此时查找指针在图中“3”位置处)此时这个值肯定在13结点和23结点之间。查找指针到下一层继续查找。
此时,我们和19进行推断。找到了。
redis中跳跃链表数据结构
那redis是如何实现跳跃链表的呢?
在redis 5中,定义的数据结构如下:
// 跳跃表节点
typedef struct zskiplistNode {
sds ele;// 用于存储字符串类型的数据
double score; //用于存储排序的分值。
struct zskiplistNode *backward; //后退指针, 只能指向当前节点最底层的前一个节
//点, 头节点和第一个节点——backward指向NULL, 从后向前遍历跳
//跃表时使用。
struct zskiplistLevel {
struct zskiplistNode *forward;// 指向本层的下一个节点
unsigned int span; //forward指向的节点与本节点之间的元素个数。 span值越
//大, 跳过的节点个数越多。
} level[];// 数组 最长64 ,
} zskiplistNode;
//跳跃表对象
typedef struct zskiplist {
struct zskiplistNode *header,*tail;
unsigned long length;
int level;
} zskiplist;
需要注意 :zsKiplist 的head 是一个一个特殊节点, 它的level数组元素个数为64。 头节点在有序集合中不存储任何member和score值, ele值为NULL, score值为0; 也不计入跳跃表的总长度。 头节点在初始化时, 64个元素的forward都指向NULL, span值都为0。
最后示意图如下:
基本上看了完这个数据结构 对redis的zset 也就大致了解了;