5、认识redis的zset集合

介绍

Zset,即有序集合(Sorted Set),是 Redis 提供的一种复杂数据类型。Zset 是 set 的升级版,它在 set 的基础上增加了一个权重参数 double类型的分数值(即score),使得集合中的元素能够按 score 进行有序排列。

zset的listpack结构

当满足以下两个条件时, zset采用listpack结构存储数据

  1. 集合中元素数量小于等于128个
  2. 集合中每个元素小于等于64k

存储结构如下
在这里插入图片描述

两个条目为一组, 分别存储实际值和score, 同时listpack也支持查询步长+1的跨越条目查询, listpack本身是无序的,但默认最多只会存储64条所以每次获取时才扫描顺序

zset的skiplist+dict结构

当zset中的元素不满足以上两个条件时, 会采用skiplist + dict存储数据

skiplist 是基于随机算法的一种有序链表,其查询效率与红黑树的查询效率差不多,都是 O(logn),但是 skiplist 的结构远比复杂的红黑树简单很多。下面我们就深入介绍一下 skiplist 的结构。

Redis 定义了一个 zskiplist 结构体来抽象 skiplist 这一数据结构,其具体定义如下:

// 跳表本体
typedef struct zskiplist {
  struct zskiplistNode *header, *tail; // 指向skiplist结构的首节点和尾结点
  unsigned long length; // 当前zskiplist level0(底层)的节点个数,也是其中存储元素的个数
  int level; // 当前zskiplist的层级数
} zskiplist;

// 跳表节点
typedef struct zskiplistNode {
    sds ele;                  // 元素值(成员)
    double score;              // 分值
    struct zskiplistNode *backward;   // 后退指针
    struct zskiplistLevel {   
        struct zskiplistNode *forward;  // 前进指针
        unsigned int span;              // 跨度(指向目标节点之间的距离)
    } level[];
} zskiplistNode;

跳表的查找、插入和删除操作的时间复杂度都是 O(logN),其中 N 是跳跃表中的元素数量。这使得跳表在处理大量数据时具有很高的性能。跳表在链表的基础上增加了多级索引,通过多级索引位置的专跳,实现了快速查找元素

我们常见的有序链表结构如下图所示,每个节点里面有两个关键部分,一个是存放的数据,还有一个是指向下一个节点的 next 指针。
在这里插入图片描述

假设要在这个链表里面查询 48 这个节点,我们需要从 header 节点开始向后遍历,每遍历到一个节点,都要比较节点存储的值与 48 是否一致。最差的情况就是遍历完整个链表,时间复杂度也就是 O(N)。

skiplist 在普通有序链表的基础之上,添加多层有序链表,如下图所示:
在这里插入图片描述

使用 skiplist 查找数据的时候,会先从最高层,也就是上图中的 level 2,开始向后遍历,这里发现 level 2 层小于 48 的最大节点是 18 节点,那么向下一层来到 level 1,继续从 18 节点向后遍历。在 level 1 层中发现小于 48 的最大节点是 35,那么再向下一层来到 level 0,继续从 35 节点向后迭代,最终找到 48 这个目标节点。

整个查找路径如下图红色箭头所示:
在这里插入图片描述

通过这个示例,我们可以体会到使用 skiplist 在结构上的特点:维护多层有序链表的索引,越往上层索引越稀疏,比如,level 2 就比 level 1 稀疏。在查找过程中,这些稀疏索引可以帮助我们跳过一些不必要的匹配操作。在利用 skiplist 这种稀疏索引进行查找的时候,我们需要使用到 cur 和 next 两个指针,当 next 节点的值比目标值大或是迭代到 NULL 时,则下降一层继续从 cur 开始向后遍历,直至找到目标节点或者返回 NULL。

skiplist+dict结构图大致如下(借鉴图)
在这里插入图片描述

使用dict存储value, 使用skiplist存储socre

Redis跳表与MySQLB+树对比

特性B+树跳表
数据结构多叉平衡树(每个节点可以有多个子节点),非叶子节点存索引,叶子存数据多层链表结构,底层存数据,上层为索引,每个节点只有一个下一节点
查询复杂度O(logN)O(logN)
插入复杂度O(logN)O(logN)
删除复杂度O(logN)O(logN)
区间查询高效,通过叶子节点链表实现范围查询高效,逐层下降并线性遍历范围内数据
空间开销每个节点存多个元素,占用空间少多级索引链表,占用更多指针空间
磁盘友好性磁盘 IO 友好,适合数据库和存储系统; 以页(通常4k)为单位读写内存结构,不适合磁盘存储
稳定性高效稳定,性能接近二分查找稳定性略逊于 B+树,随机性较强
实现复杂度实现复杂,涉及节点分裂和合并实现简单,易于维护
缓存性能数据顺序存储在叶子节点,缓存命中率高链表遍历,不利于缓存命中
使用场景数据库存储、磁盘存储Redis Zset、内存排序与排名

结论

  • 数据库索引:选 B+树(磁盘 IO 更少)。
  • 内存有序集合:选跳表(易实现、效率高)。
  • 查询效率:在内存中,跳表略优;在磁盘上,B+树更优

个人公众号: 行云代码

参考文章

说透 Redis 7

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

uncleqiao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值