目录
ZSet的编码方式
什么是跳跃列表(skiplist)?
ZSet的底层结构
跳跃列表的查询过程
ZSet的编码方式
Redis中的有序集合zset底层实现采用了两种编码方式:
- REDIS_ENCODING_SKIPLIST 跳跃列表
- REDIS_ENCODING_ZIPLIST 压缩列表
对于不同编码的触发方式/条件,有两个关键因素:
- zset-max-ziplist-entries 128
- zset-max-ziplist-value 64
当有序集合的元素个数 ≥ zset-max-ziplist-entries (128个) ,或每个元素成员的长度≥zset-max-ziplist-value (默认为64字节)时,使用跳跃列表和哈希表作为有序集合的内部实现。
拓展:
可以通过在Redis配置文件中(redis.conf)中定义使用两种不同编码的时机。
什么是跳跃列表(skiplist)?
跳跃列表是一种有序的数据结构(有些版本会说是一种随机化的数据结构,这里的随机化是指概率),由William Pugh在论文《Skip Lists: A Probabilis ... Trees》中提出。根据不同层级定向引导每一节点指向后续节点,达到快速访问指定节点的目的。跳跃列表在查找指定节点时,平均时间复杂度为O(logN),最坏情况下时间复杂度为O(N)。
ZSet的底层结构
/* ZSETs use a specialized version of Skiplists */
/// ele: 当前节点元素,用以存储数据
/// score: 当前元素成员所对应的分数
/// backward:用以指向前驱节点的指针,即回溯指针
/// level:用以表示层级,每一层级对应指向一个指针 forward
/// forward:指向位于表尾方向其他节点的指针。
/// span:当前节点到forward指向的节点所跨越的节点个数。
typedef struct zskiplistNode {
sds ele;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned long span;
} level[];
} zskiplistNode;
/// header: 指向头部跳跃表节点的指针。
/// tail:指向尾部跳跃表节点的指针。
/// length:表示跳跃表中的节点总数。
/// level:表示跳跃表中层数最大的节点的层数,表头节点的层数不计算在内。
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
从图例和代码中我们不难发现,Redis中zset结构体由zskiplist和dict组成,同时zskiplist结构体中包含有跳跃表节点 zskiplistNode 和 跳跃表层级 zskiplistLevel,以此组成了一个双向链表结构。
跳跃列表的查询过程
接下来让我们分析一下跳跃表在查询时的过程。
在上图中,跳表层级为4层,元素个数为10个,按照自顶向下的顺序进行查找,从顶层(第4层)开始查找元素值17:
- 第四层:找到元素值7,但发现7≤17,继续往右找;找到元素值29,但发现7≤29,回溯到上一指向元素值7的指针并向下走一层
- 第三层:找到元素值25,但发现25≥17,回溯到指向元素值7的指针并向下走一层
- 第二层:找到元素值16,但发现16≤17,继续往右找;找到元素值17,命中查找值,检索成功
在下一章节,我们将就ZSet中使用的算法和代码实现作进一步阐析,并对性能和背后的数学原理作进一步探究。