zoukankan      html  css  js  c++  java
  • Redis之SkipList数据结构

    0.前言

    Redis中有序集合zset需要使用skiplist作为存储数据结构, 关于skiplist数据结构描述可以查询wiki, 本文主要介绍Redis实现的skiplist的细节.

    1.数据结构定义

    typedef struct zskiplistNode {
         /*成员object对象*/
        robj *obj;
         /*分数字段依赖此值对skiplist进行排序*/
        double score;
         /*插入层中指向上一个元素level数组*/
        struct zskiplistNode *backward;
        struct zskiplistLevel {
              /*每层中指向下一个元素指针*/
            struct zskiplistNode *forward;
              /*距离下一个元素之间元素数量, 即forward指向的元素*/
            unsigned int span;
        } level[];
    } zskiplistNode;
    
    typedef struct zskiplist {
         /*跳跃表头节点和尾节点*/
        struct zskiplistNode *header, *tail;
         /*跳跃表中元素个数*/
        unsigned long length;
         /*跳跃表当前最大层数*/
        int level;
    } zskiplist;
    

    2.创建跳跃表

    创建跳跃表过程比较简单, 初始化zskiplist数据结构, 跳跃表默认最大层数32层, 跳跃表是按score进行升序排列.

    /*创建跳跃表*/
    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;
    }
    /*创建一个跳跃表节点*/
    zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
        zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
        zn->score = score;
        zn->obj = obj;
        return zn;
    }
    

    3.添加元素

    zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
        zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
        unsigned int rank[ZSKIPLIST_MAXLEVEL];
        int i, level;
    
        redisAssert(!isnan(score));
        x = zsl->header;
         /*从头节点开始搜索, 一层一层向下搜索, 直到直到最后一层, update数组中保存着每层应该插入的位置*/
        for (i = zsl->level-1; i >= 0; i--) {
            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 &&
                    compareStringObjects(x->level[i].forward->obj,obj) < 0))) {
                   /*记录每层距离头部位置的距离*/
                rank[i] += x->level[i].span;
                x = x->level[i].forward;
            }
            update[i] = x;
        }
        /* 随机一个层数, 如果随机的层数是新的层数, 则需要给update数组中新的层数赋值*/
        level = zslRandomLevel();
        if (level > zsl->level) {
            for (i = zsl->level; i < level; i++) {
                rank[i] = 0;
                   /*新的一层上一个指针肯定是header*/
                update[i] = zsl->header;
                update[i]->level[i].span = zsl->length;
            }
            zsl->level = level;
        }
         /*创建新的节点插入到update数组对应的层*/
        x = zslCreateNode(level,score,obj);
        for (i = 0; i < level; i++) {
            x->level[i].forward = update[i]->level[i].forward;
            update[i]->level[i].forward = x;
            /*
             header                update[i]     x    update[i]->forward         
              |-----------|-----------|-----------|-----------|-----------|-----------|
                                      |<---update[i].span---->|
              |<-------rank[i]------->|                      
              |<-------------------rank[0]------------------->|
             
              更新update数组中span值和新插入元素span值, rank[0]存储的是x元素距离头部的距离, rank[i]存储的是update[i]距离头部的距离, 上面给出了示意图
              */
            x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
            update[i]->level[i].span = (rank[0] - rank[i]) + 1;
        }
    
        /* level可能小zsl->level, 无变动的元素span依次增加1*/
        for (i = level; i < zsl->level; i++) {
            update[i]->level[i].span++;
        }
         /*上一个元素level数组, 重新赋值*/
        x->backward = (update[0] == zsl->header) ? NULL : update[0];
        if (x->level[0].forward)
            x->level[0].forward->backward = x;
        else
              /*下一个元素为空,则表示x为尾部元素*/
            zsl->tail = x;
        zsl->length++;
        return x;
    }
    

    4.获取排名

    排名其实就是元素在skiplist中排列的序号, 获取排名需要给出分数和成员member, 通过score查找, 匹配member成员, 时间复杂度log(N). 由于skiplist是升序排列的,因此函数返回的rank是score按升序排列的rank, 如果想获取降序rank应该是(length-rank).

    unsigned long zslGetRank(zskiplist *zsl, double score, robj *o) {
        zskiplistNode *x;
        unsigned long rank = 0;
        int i;
    
        x = zsl->header;
         /*循环遍历并累加每层的span值, 获取总的排名*/
        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 &&
                    compareStringObjects(x->level[i].forward->obj,o) <= 0))) {
                rank += x->level[i].span;
                x = x->level[i].forward;
            }
    
            /* 判断成员是否相等 */
            if (x->obj && equalStringObjects(x->obj,o)) {
                  /*升序排列的排名*/
                return rank;
            }
        }
        return 0;
    }
    

    5.根据排名查找元素

    /*通过排名查找元素, rank是从1开始, rank是升序排列的rank值*/
    zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
        zskiplistNode *x;
        unsigned long traversed = 0;
        int i;
    
         /*遍历每一层,并记录排名, 与待查rank比较, 相等则找到, 找不到则返回NULL*/
        x = zsl->header;
        for (i = zsl->level-1; i >= 0; i--) {
            while (x->level[i].forward && (traversed + x->level[i].span) <= rank)
            {
                traversed += x->level[i].span;
                x = x->level[i].forward;
            }
              /*找到直接返回*/
            if (traversed == rank) {
                return x;
            }
        }
        return NULL;
    }
    

    6.删除元素

    删除元素需要精确匹配到分数和member

    /*删除一个元素*/
    int zslDelete(zskiplist *zsl, double score, robj *obj) {
        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 &&
                    compareStringObjects(x->level[i].forward->obj,obj) < 0)))
                x = x->level[i].forward;
            update[i] = x;
        }
        /* 由于score值可能相等, 因此需要精确匹配score和obj值 */
        x = x->level[0].forward;
        if (x && score == x->score && equalStringObjects(x->obj,obj)) {
            zslDeleteNode(zsl, x, update);
            zslFreeNode(x);
            return 1;
        }
        return 0; /* not found */
    }
    
    /* 具体进行删除元素所在节点*/
    void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
        int i;
         /*删除元素需要更新update元素的span值*/
        for (i = 0; i < zsl->level; i++) {
            if (update[i]->level[i].forward == x) {
                update[i]->level[i].span += x->level[i].span - 1;
                update[i]->level[i].forward = x->level[i].forward;
            } else {
                update[i]->level[i].span -= 1;
            }
        }
        if (x->level[0].forward) {
              /*非尾部元素则需要重置backforward指针*/
            x->level[0].forward->backward = x->backward;
        } else {
              /*删除x可能是最后一个元素, 需要重置尾部指针*/
            zsl->tail = x->backward;
        }
         /*删除元素位于最上层, 并且仅有此一个元素, 删除之后,需要降低跳跃表层数*/
        while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
            zsl->level--;
        zsl->length--;
    }
    
  • 相关阅读:
    kafka集群搭建
    更改:把redis替换成kafka
    mysql+canal+kafka+elasticsearch构建数据查询平台
    zookeeper集群搭建
    另类--kafka集群中jmx端口设置
    kafka集群中jmx端口设置
    使用zookeeper报错 stat is not executed because it is not in the whitelist. envi is not executed because it is not in the whitelist.
    使用python的kazoo模块连接zookeeper实现最基本的增删改查
    Maven之阿里云镜像仓库配置
    通过yum安装maven
  • 原文地址:https://www.cnblogs.com/ourroad/p/4899066.html
Copyright © 2011-2022 走看看