redis中跳表zset的具体使用

 更新时间:2024年01月15日 09:40:48   作者:大牛写代码  
Redis跳表zset是一种结合了跳表和有序集合的高效数据结构,适用于实现排序和大规模数据的快速查询,本文主要介绍了redis中跳表zset的具体使用,感兴趣的可以了解一下

跳表的基本思想

Skip List(跳跃列表)这种随机的数据结构,可以看做是一个二叉树的变种,它在性能上与红黑树、AVL树很相近;但是Skip List(跳跃列表)的实现相比前两者要简单很多,目前Redis的zset实现采用了Skip List(跳跃列表)。

在这里插入图片描述

特点

1、分层,每层由有序链表构成
2、头节点在每层出现
3、某个节点如果在上层出现,那在下层也出现
4、节点的层数是随机的

节点与结构

跳跃表节点zskiplistNode

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

属性

  • ele:存储字符串数据
  • score:存储排序分值
  • *backward:指针,指向当前节点最底层的前一个节点
  • level[]:柔性数组,随机生成1-64的值
  • forward:指向本层下一个节点
  • span:本层下个节点到本节点的元素个数

跳跃表链表

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

属性

  • header,tail:头节点和尾节点
  • length:跳跃表长度(不包括头节点)
  • tail:跳跃表高度

跳表的设计思想和优势

1、能够同时拥有链表和数优势的数据结构,既有链表插入快的特点又有数组查询快的特点
2、随机跨越层数
3、最底层的链表是双向链表,包含所有元素
4、对于有序链表查询优化,相比较于平衡数来说,更好实现
5、内存占用上来看,相比较于平衡数会更少

API解析

Tip:以下的zsl为zskiplist

zslCreate(创建跳跃表)

/* Create a new skiplist. */
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    zsl = zmalloc(sizeof(*zsl));
    zsl->level = 1;
    zsl->length = 0;
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    return zsl;
}

大致流程:
1、定义一个zsl,申请内存,赋初始值
2、调用zslCreateNode创建出头节点
3、每层头节点赋初始值
4、尾节点赋null值

zslCreateNode(创建节点)

/* Create a skiplist node with the specified number of levels.
 * The SDS string 'ele' is referenced by the node after the call. */
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
    zskiplistNode *zn =
        zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score;
    zn->ele = ele;
    return zn;
}

大致流程:
1、申请内存(节点内存和柔性数组的内存)
2、属性赋值

zslGetRank(查找排位)

排位就是累积跨越的节点数量

unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *x;
    unsigned long rank = 0;
    int i;

    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))) {
            rank += x->level[i].span;
            x = x->level[i].forward;
        }

        /* x might be equal to zsl->header, so test if obj is non-NULL */
        if (x->ele && x->score == score && sdscmp(x->ele,ele) == 0) {
            return rank;
        }
    }
    return 0;
}

大致流程:
1、从最上层开始遍历节点并对比元素,对比score
2、如果当前分值大雨下一个分值,则累加span(比对分值,如果分值一样就比对ele)
3、指向本层的下一个节点
4、如果找到了,也就是ele相同,则返回

zslDelete(删除节点)

int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    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;
        }
        update[i] = x;
    }
    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. */
    x = x->level[0].forward;
    if (x && score == x->score && sdscmp(x->ele,ele) == 0) {
        zslDeleteNode(zsl, x, update);
        if (!node)
            zslFreeNode(x);
        else
            *node = x;
        return 1;
    }
    return 0; /* not found */
}

大致流程:
1、遍历跳表
2、比对分值,比对ele
3、如分值小于或等于当前值,并且ele不相等,继续下一个并记录节点
4、如分值和ele都相同,调用zslDeleteNode删除该节点

跳表是在很多排名以及分数相关的场景中使用频率极高的数据结构,也是设计的极其巧妙的一种结构,希望本篇文章能帮助各位更加深入的理解这种结构。

到此这篇关于redis中跳表zset的具体使用的文章就介绍到这了,更多相关redis 跳表zset内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现多级缓存的方法详解

    Java实现多级缓存的方法详解

    对于高并发系统来说,有三个重要的机制来保障其高效运行,它们分别是:缓存、限流和熔断,所以本文就来和大家探讨一下多级缓存的实现方法,希望对大家有所帮助
    2024-02-02
  • 关于在Redis中使用Pipelining加速查询的问题

    关于在Redis中使用Pipelining加速查询的问题

    这篇文章主要介绍了在Redis中使用Pipelining加速查询,Redis是一个client-server模式的TCP服务,也被称为Request/Response协议的实现,本文通过一个例子给大家详细介绍,感兴趣的朋友一起看看吧
    2022-05-05
  • redission分布式锁防止重复初始化问题

    redission分布式锁防止重复初始化问题

    这篇文章主要介绍了redission分布式锁防止重复初始化问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • 基于Redis实现基本抢红包算法详解

    基于Redis实现基本抢红包算法详解

    [key, value]的缓存数据库, Redis官方性能描述非常高, 所以面对高并发场景, 使用Redis来克服高并发压力是一个不错的手段, 本文主要基于Redis来实现基本的抢红包系统设计,感兴趣的朋友跟随小编一起看看吧
    2024-04-04
  • Redis如何正确关闭和开启持久化

    Redis如何正确关闭和开启持久化

    本文主要介绍了Redis如何正确关闭和开启持久化,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • 解决redis-cli报错Could not connect to Redis at 127.0.0.1:6379: Connection refused

    解决redis-cli报错Could not connect to Redis&

    这篇文章主要介绍了解决redis-cli报错Could not connect to Redis at 127.0.0.1:6379: Connection refused,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Redis+Lua脚本实现计数器接口防刷功能(升级版)

    Redis+Lua脚本实现计数器接口防刷功能(升级版)

    这篇文章主要介绍了Redis+Lua脚本实现计数器接口防刷功能,使用脚本使得set命令和expire命令一同达到Redis被执行且不会被干扰,在很大程度上保证了原子操作,对Redis实现计数器接口防刷功能感兴趣的朋友一起看看吧
    2022-02-02
  • Redis6.0搭建集群Redis-cluster的方法

    Redis6.0搭建集群Redis-cluster的方法

    这篇文章主要介绍了Redis6.0搭建集群Redis-cluster的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • 如何监听Redis中Key值的变化(SpringBoot整合)

    如何监听Redis中Key值的变化(SpringBoot整合)

    测试过程中我们有一部分常量值放入redis,共大部分应用调用,但在测试过程中经常有人会清空redis,回归测试,下面这篇文章主要给大家介绍了关于如何监听Redis中Key值变化的相关资料,需要的朋友可以参考下
    2024-03-03
  • 基于Redis实现抽奖功能及问题小结

    基于Redis实现抽奖功能及问题小结

    这篇文章主要介绍了基于Redis实现抽奖功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-08-08

最新评论