Redis基础数据结构之 Sorted Set 有序集合 源码解读

Sorted Set 是什么?

有序集合(Sorted Set)是 Redis 中一种重要的数据类型,它本身是集合类型,同时也可以支持集合中的元素带有权重,并按权重排序

  • ZRANGEBYSCORE:按照元素权重返回一个范围内的元素
  • ZSCORE:返回某个元素的权重值

在这里插入图片描述

Sorted Set 数据结构

  • 结构定义:server.h
  • 实现:t_zset.c

结构定义是 zset,里面包含哈希表 dict 和跳表 zsl。zset 充分利用了:

  • 哈希表的高效单点查询特性(ZSCORE)
  • 跳表的高效范围查询(ZRANGEBYSCORE)

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

Skiplist:用于快速查找、插入和删除操作,提供近似O(log N)的时间复杂度

Dictionary(Hashtables):用来存储成员与分数的映射关系,确保每个成员的唯一性

跳表(skiplist)

多层的有序链表。下面展示的是 3 层的跳表,头节点是一个 level 数组,作为 level0~level2 的头指针

在这里插入图片描述

跳表节点的结构定义

typedef struct zskiplistNode {
   
   
    // sorted set 中的元素
    sds ele;
    // 元素权重
    double score;
    // 后向指针(为了便于从跳表的尾节点倒序查找)
    struct zskiplistNode *backward;
    // 节点的 level 数组
    struct zskiplistLevel {
   
   
        // 每层上的前向指针
        struct zskiplistNode *forward;
        // 跨度,记录节点在某一层 *forward 指针和该节点,跨越了 level0 上的几个节点
        unsigned long span;
    } level[];
} zskiplistNode;

跳表的定义

typedef struct zskiplist {
   
   
    // 头节点和尾节点
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

在这里插入图片描述

跳表节点查询

在查询某个节点时,跳表会从头节点的最高层开始,查找下一个节点:
访问下一个节点

  • 当前节点的元素权重 < 要查找的权重
  • 当前节点的元素权重 = 要查找的权重,且节点数据<要查找的数据
    访问当前节点 level 数组的下一层指针
    当前节点的元素权重 > 要查找的权重
//获取跳表的表头
x = zsl->header;
//从最大层数开始逐一遍历
for (i = zsl->level-1; i >= 0; i--) {
   
   
   ...
   while (x->level[i].forward && (x->level[i].forward->score < score || (x->level[i].forward->score == score 
    && sdscmp(x->level[i].forward->ele,ele) < 0))) {
   
   
      ...
      x = x->level[i].forward;
    }
    ...
}

层数设置

几种方法:

  • 每层的节点数约是下一层节点数的一半。
    • 好处:查找时类似于二分查找,查找复杂度可以减低到 O(logN)
    • 坏处:每次插入/删除节点,都要调整后续节点层数,带来额外开销

随机生成每个节点的层数。Redis 跳表采用了这种方法。
Redis 中,跳表节点层数是由 zslRandomLevel 函数决定。

int zslRandomLevel(void) {
   
   
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

其中每层增加的概率是 0.25,最大层数是 32。

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
跳表插入节点 zslInsert
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
   
   
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));
    x = zsl->header;
    // 从最高层的 level 开始找
    for (i = zsl->level-1; i >= 0; i--) {
   
   
        // 每层待插入的位置
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
        // forward.score < 待插入 score || (forward.score < 待插入 score && forward.ele < ele)
        while (x->level[i].forward &&
               (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                 sdscmp(x->level[i].forward->ele, ele) < 0))) {
   
   
            // 在同一层 level 找下一个节点
            rank
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

无休居士

感谢您的支持,我会继续努!

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

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

打赏作者

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

抵扣说明:

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

余额充值