首页 > 数据库 >[redis 源码走读] - 跳跃表(skiplist)

[redis 源码走读] - 跳跃表(skiplist)

时间:2024-06-03 20:58:05浏览次数:29  
标签:结点 score level 走读 redis ele 源码 zsl

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

1. 数据结构

跳跃表是一个有序的双向链表。理解 zskiplistNode 的 zskiplistLevel 是理解zskiplist工作流程的关键。

 
    /* 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; // 当前结点与 forward 指向的结点距离(跨越多少个结点),排名中应用。
        } level[]; // 层,可以理解结点的垂直纬度。
    } zskiplistNode;
    
    typedef struct zskiplist {
        struct zskiplistNode *header, *tail;
        unsigned long length;
        int level;
    } zskiplist;

2. 思路

跳跃表是链表,链表查找时间复杂度是 O(n),一般情况下,顺序查找比较慢。那比较取巧的,因为数据是顺序的,我们可以跳着找。例如下面 1 - 13 的数字,我们要找 9 这个数字。跳着找的流程是这样的:

在第三步发现 11 比 9 大,就尝试跳更小的间距寻找合适的数据。同样的以此类推直到找到我们需要的数据。这样比我们顺序找要快很多。 我们可以拆分一下上图的查找流程。每次查找不到时,就重新定向查找。每次重新定向查找被看作一个层。

链表的层次,类似一个二维空间。每个结点有若干层,每一层将结点连接在一起建立关系,查找时 level 从最高层自上而下,结点从左到右。

随机层 level,层数越高,概率越小。

 
    #define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
    #define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
    
    int zslRandomLevel(void) {
        int level = 1;
        // 每增加一层概率是 ZSKIPLIST_P,所以层数越高,概率越小。
        while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
            level += 1;
        // 最高层数 ZSKIPLIST_MAXLEVEL
        return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
    }

3. 接口

3.1. 插入结点

sorted set 功能实现,跳跃表结合 dict 使用。

 
    // 跳跃表并不是单独使用的,在 sorted set 中,结合 dict 使用。
    typedef struct zset {
        dict *dict; // 保存 ele 数据作为 key
        zskiplist *zsl; // 跳跃表存储 ele
    } zset;
    
    int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
        ...
        znode = zslInsert(zs->zsl,score,ele);
        serverAssert(dictAdd(zs->dict,ele,&znode->score) == DICT_OK);
        ...
    }

跳跃表插入结点。

 
    /* Insert a new node in the skiplist. Assumes the element does not already
     * exist (up to the caller to enforce that). The skiplist takes ownership
     * of the passed SDS string 'ele'. */
    zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
        // update 保存每层遍历到满足条件的最后一个结点。
        zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    
        // rank 排名保存 span 结点间距。
        unsigned int rank[ZSKIPLIST_MAXLEVEL];
        int i, level;
    
        // 二维空间,level自上而下遍历,结点从头到尾遍历,找到合适的插入结点位置。
        serverAssert(!isnan(score));
        x = zsl->header;
        for (i = zsl->level-1; i >= 0; i--) {
            // 下层保存上层的步距。
            /* store rank that is crossed to reach the insert position */
            rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
            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[i] += x->level[i].span;
                // 遍历下一个结点。
                x = x->level[i].forward;
            }
    
            // 保存 i 层满足条件的最后一个结点。
            update[i] = x;
        }
    
        /* we assume the element is not already inside, since we allow duplicated
         * scores, reinserting the same element should never happen since the
         * caller of zslInsert() should test in the hash table if the element is
         * already inside or not. */
        // 随机层数
        level = zslRandomLevel();
        if (level > zsl->level) {
            // 初始化新增加的层,指向头结点,步距是列表的长度(结点个数)。
            for (i = zsl->level; i < level; i++) {
                rank[i] = 0;
                update[i] = zsl->header;
                // 新增的 level 上是有指向结点指针的。
                update[i]->level[i].span = zsl->length;
            }
            zsl->level = level;
        }
    
        // 创建新的结点保存数据。
        x = zslCreateNode(level,score,ele);
    
        // 插入结点到列表
        for (i = 0; i < level; i++) {
            // level 自下而上与同层的插入位置前后结点建立联系。
            x->level[i].forward = update[i]->level[i].forward;
            update[i]->level[i].forward = x;
    
            /* update span covered by update[i] as x is inserted here */
            // span:在同一个层级,当前结点到下一个结点的距离。
            // rank[0] - rank[i] 是插入位置,到 i 层所在结点的,结点距离。
            // update[i]->level[i].span 是插入位置到下一个结点到距离。
            x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
    
            // 在 update[i] 后面添加了一个结点,span + 1
            update[i]->level[i].span = (rank[0] - rank[i]) + 1;
        }
    
        /* increment span for untouched levels */
        for (i = level; i < zsl->level; i++) {
            update[i]->level[i].span++;
        }
    
        // 处理双向结点的前后结点连接关系。
        x->backward = (update[0] == zsl->header) ? NULL : update[0];
        if (x->level[0].forward)
            x->level[0].forward->backward = x;
        else
            zsl->tail = x;
        zsl->length++;
        return x;
    }

3.2. 流程描述

当 level 为 2 的链表,插入 level 为 3 的结点 5。(这里忽略了 ele 和 score 的处理)

插入数据的流程其实比不复杂,对于源码的理解,最好结合图表,这样大脑思考比较便捷。

  • 插入前

  • 插入后


4. 调试

可以修改 redis 源码,跟踪一下工作流程。

server.c

 
    int main(int argc, char **argv) {
        struct timeval tv;
        int j;
    
        zsetTest();
        return -1;
        ...
    }

t_zset.c

 
    void zsetTest() {
        sds ele;
        double score;
        zskiplist *zsl;
        zskiplistNode * node;
    
        score = 1;
        ele = sdsfromlonglong(11);
    
        zsl = zslCreate();
        node = zslInsert(zsl, score, ele);
    
        score = 2;
        ele = sdsfromlonglong(22);
        node = zslInsert(zsl, score, ele);
        zslFree(zsl);
    }

标签:结点,score,level,走读,redis,ele,源码,zsl
From: https://blog.csdn.net/smart_an/article/details/139424329

相关文章

  • 【源码】【SpringBoot】Web课程设计学生成绩管理系统的设计与开发
    学生成绩管理系统系统功能开发环境开发技术前端技术后端技术系统展示登录界面学生身份登录教师身份登录源码获取↓↓↓↓:源码可在后台私信联系博主或文末添加博主微信获取帮助系统功能系统用户身份分为三类,学生、教师和辅导员。身份不同登陆后所具有的系统权限......
  • 【精品毕设】汽车配件销售企业连锁店管理系统(包含论文和源码)
    摘要目前汽车配件销售企业大多数在其连锁店的管理还是手工进行,随着汽车配件行业的迅速发展,手工管理的种种弊端暴露无疑,给销售企业的发展带来了不必要的麻烦。为了规范企业内部管理,提高企业业务管理水平,更好的为客户服务,应采用计算机来管理汽车配件的进销存业务。本文首先对......
  • 【精品毕设】基于JavaEE的线上考试系统设计实现(包含源码和论文)
    摘 要随着计算机技术的迅猛发展,学校教学和管理的信息化发展也有长足的进步,这就要求各个环节都均衡发展,从软硬件双方面把学校建设成一流的信息管理、教育教学的平台。本文设计开发的考试管理系统也是其中重要的一个方面。该系统本着减轻教师工作负担、提高工作效率、优化学生......
  • java毕业设计之网上书城系统(ssm项目源码+LW+PPT)
    1项目介绍本系统主要包括管理员和用户;主要包括:个人中心、用户管理、图书类型管理、热卖图书管理、管理员管理、系统管理、订单管理等功能的管理系统。2、项目技术项目后端框架:Java+ssm项目前端框架:vue2,ssm3、开发环境ssm+vue环境说明:环境说明:开发语言:java框架:ssm......
  • java毕业设计之校园旧书交易交换平台(ssm项目源码+LW+PPT)
    1项目介绍本系统主要包括以下功能模块:主页、个人中心、学生管理、发布人管理、书籍分类管理、书籍信息管理、交易信息管理、交换信息管理、系统管理等模块,通过这些模块的实现能够基本满足日常校园旧书交易交换的操作。2、项目技术项目后端框架:Java+ssm项目前端框架:vue......
  • java毕业设计之影院管理系统(ssm项目源码+LW+PPT)
    1项目介绍本系统主要针对管理员和用户角色,主要包括:个人中心,电影信息管理,电影类型管理,系统管理,订单管理等功能的界面。2、项目技术项目后端框架:Java+ssm项目前端框架:vue2,ssm3、开发环境ssm+vue环境说明:环境说明:开发语言:java框架:ssm,vueJDK版本:JDK1.8数据库:mysql......
  • synchronized、Lock本地锁和Redisson分布式锁的简单使用
    文章目录概念准备工作synchronized本地锁演示JUC包的Lock本地锁演示Redisson的RLock分布式锁演示源码地址参考来源概念redisson是一个简单易用的Redis客户端工具。不仅如此,它还具备分布式锁的功能准备工作快速整合SSMP请参考我这篇文章SpringBoot快速整合Spring......
  • redis - [02] 安装部署
    在Windows和Linux操作系统下的安装部署  一、windows版(1)访问redis下载地址:https://github.com/tporadowski/redis/releases(2)将Redis-x64-5.0.14.1.zip下载并解压到合适的目录(3)打开cmd窗口,切换到该目录下运行:redis-server.exeredis.windows.conf运行之后,redis服务就......
  • windows下python源码编译构建grpc【填各种坑】
    背景首先这是巨坑,pipinstallgrpcio和pipinstallgrpcio_tools的方式,因为公司的库里没有,且申请入库复杂,因此只能通过源码构建。思路第一步,应该是要去找grpc的源码,公司是有源码的,也可以在PIPY上找,比如我需要1.41.1的grpc版本,就如下地址https://pypi.org/project/......
  • 校园圈子系统--自带校园跑腿功能,校园交友,校园陪玩,校园交友墙,地图找伴,二手市场等功能。
    一、需求分析在搭建校园论坛平台之前,我们需要进行详细的需求分析。这包括以下几个方面:1.用户需求我们需要了解目标用户群体的需求和喜好,包括学生的年龄层次、兴趣爱好、关注话题等。通过调查问卷、访谈等方式收集用户需求,为平台开发提供依据。2.功能需求根据用户需求,我们......