08 关于 skiplist

Posted 蓝风9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了08 关于 skiplist相关的知识,希望对你有一定的参考价值。

前言

关于 redis 的数据结构 skiplist 

相关介绍主要围绕着如下测试用例, 来看看 skiplist 的存储, 以及 相关的 api 

本文的 skiplist 相关代码 拷贝自 redis-6.2.0  

代码来自于 https://redis.io/ 

单元测试

//
// Created by Jerry.X.He on 2021/3/4.
//

#include<iostream>
#include "../libs/util.h"

using namespace std;

// Test02SkiplistUsage.cpp
int main(int argc, char **argv) {

    sds head = sdsnew("head");
    sds tail = sdsnew("tail");
    sds insertAfter = sdsnew("insertAfter");

    zrangespec rangeSpec;
    rangeSpec.min = 2;
    rangeSpec.max = 10;
    zlexrangespec lexRangeSpec;
    lexRangeSpec.min = sdsfromlonglong(2);
    lexRangeSpec.max = sdsfromlonglong(8);

    static dictType dt = {
            dictSdsHash,                /* hash function */
            NULL,               /* key dup */
            NULL,               /* val dup */
            dictSdsKeyCompare,          /* key compare */
            dictSdsDestructor,          /* key destructor */
            NULL,           /* val destructor */
    };

    zskiplist *list = zslCreate();

    // zslInsert
    zslInsert(list, 0, head);
    for (int i = 0; i < 10; i++) {
        zslInsert(list, i, sdsfromlonglong(i));
    }
    zslInsert(list, 1000, tail);

    // skiplistRepr, forDebug
    skiplistRepr(list);

    // zslInsert
    zslInsert(list, 8.2, insertAfter);

    // skiplistRepr, forDebug
    skiplistRepr(list);

    // zslRandomLevel
    int randomLevel = zslRandomLevel();

    // zslFirstInRange
    zskiplistNode *firstNodeInRange = zslFirstInRange(list, &rangeSpec);

    // zslLastInRange
    zskiplistNode *lastNodeInRange = zslLastInRange(list, &rangeSpec);

    // zslFirstInLexRange
    zskiplistNode *firstNodeInLexRange = zslFirstInLexRange(list, &lexRangeSpec);

    // zslLastInLexRange
    zskiplistNode *lastNodeInLexRange = zslLastInLexRange(list, &lexRangeSpec);

    // zslGetRank
    unsigned long rankOfTem = zslGetRank(list, 9, sdsfromlonglong(9));

    // zslGetRank, score 和 ele 在 skiplist 中不匹配的情况
    unsigned long rankOfTem2 = zslGetRank(list, 3, sdsfromlonglong(2));

    // zslDelete
    zskiplistNode *delNode;
    int deleteResult = zslDelete(list, 8.2, insertAfter, &delNode);

    // zslDeleteRangeByScore
    dict *delDict = dictCreate(&dt, NULL);
    int deleteRangeResult = zslDeleteRangeByScore(list, &rangeSpec, delDict);

    int len = list->length;

    int x = 0;

}

数据结构

zskiplist 是一个链表, 不过 forword 可能会有多个层级[level], 可能指向的是下一个 元素, 或者是 NULL 

维护了元素的长度, 当前 skiplist 里面的元素最高的 level, 以及头尾节点 

zskiplistNode 表示一个 skiplist 的节点 

    里面包含了节点的元素[包含 score, ele] 

    包含了指向前一个节点的指针 

    包含了指向后面节点的指针列表[当前node对应一个 level, 会对应 level个指针]

我单元测试中的这个 skiplist 结构如下[摘取的是某一次运行结果拿出的数据], 这里调试的是 "skiplistRepr(list);" 之前的 skiplist 

当然这里 稍微修改了一下 randomLevel 的算法 

整个 skiplist 所有数据如下 

[
  {
    "score": "0",
    "ele": "",
    "lvs": [
      {
        "level": "0",
        "score": "0",
        "ele": "0",
        "span": "1"
      },
      {
        "level": "1",
        "score": "0",
        "ele": "0",
        "span": "1"
      },
      {
        "level": "2",
        "score": "0",
        "ele": "head",
        "span": "2"
      },
      {
        "level": "3",
        "score": "2",
        "ele": "2",
        "span": "4"
      },
      {
        "level": "4",
        "score": "2",
        "ele": "2",
        "span": "4"
      },
      {
        "level": "5",
        "score": "5",
        "ele": "5",
        "span": "7"
      }
    ]
  },
  {
    "score": "0",
    "ele": "0",
    "lvs": [
      {
        "level": "0",
        "score": "0",
        "ele": "head",
        "span": "1"
      },
      {
        "level": "1",
        "score": "0",
        "ele": "head",
        "span": "1"
      }
    ]
  },
  {
    "score": "0",
    "ele": "head",
    "prevScore": "0",
    "prevEle": "0",
    "lvs": [
      {
        "level": "0",
        "score": "1",
        "ele": "1",
        "span": "1"
      },
      {
        "level": "1",
        "score": "1",
        "ele": "1",
        "span": "1"
      },
      {
        "level": "2",
        "score": "2",
        "ele": "2",
        "span": "2"
      }
    ]
  },
  {
    "score": "1",
    "ele": "1",
    "prevScore": "0",
    "prevEle": "head",
    "lvs": [
      {
        "level": "0",
        "score": "2",
        "ele": "2",
        "span": "1"
      },
      {
        "level": "1",
        "score": "2",
        "ele": "2",
        "span": "1"
      }
    ]
  },
  {
    "score": "2",
    "ele": "2",
    "prevScore": "1",
    "prevEle": "1",
    "lvs": [
      {
        "level": "0",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "1",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "2",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "3",
        "score": "5",
        "ele": "5",
        "span": "3"
      },
      {
        "level": "4",
        "score": "5",
        "ele": "5",
        "span": "3"
      }
    ]
  },
  {
    "score": "3",
    "ele": "3",
    "prevScore": "2",
    "prevEle": "2",
    "lvs": [
      {
        "level": "0",
        "score": "4",
        "ele": "4",
        "span": "1"
      },
      {
        "level": "1",
        "score": "5",
        "ele": "5",
        "span": "2"
      },
      {
        "level": "2",
        "score": "5",
        "ele": "5",
        "span": "2"
      }
    ]
  },
  {
    "score": "4",
    "ele": "4",
    "prevScore": "3",
    "prevEle": "3",
    "lvs": [
      {
        "level": "0",
        "score": "5",
        "ele": "5",
        "span": "1"
      }
    ]
  },
  {
    "score": "5",
    "ele": "5",
    "prevScore": "4",
    "prevEle": "4",
    "lvs": [
      {
        "level": "0",
        "score": "6",
        "ele": "6",
        "span": "1"
      },
      {
        "level": "1",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "2",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "3",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "4",
        "score": "null",
        "ele": "null",
        "span": "5"
      },
      {
        "level": "5",
        "score": "null",
        "ele": "null",
        "span": "5"
      }
    ]
  },
  {
    "score": "6",
    "ele": "6",
    "prevScore": "5",
    "prevEle": "5",
    "lvs": [
      {
        "level": "0",
        "score": "7",
        "ele": "7",
        "span": "1"
      }
    ]
  },
  {
    "score": "7",
    "ele": "7",
    "prevScore": "6",
    "prevEle": "6",
    "lvs": [
      {
        "level": "0",
        "score": "8",
        "ele": "8",
        "span": "1"
      },
      {
        "level": "1",
        "score": "8",
        "ele": "8",
        "span": "1"
      },
      {
        "level": "2",
        "score": "null",
        "ele": "null",
        "span": "3"
      },
      {
        "level": "3",
        "score": "null",
        "ele": "null",
        "span": "3"
      }
    ]
  },
  {
    "score": "8",
    "ele": "8",
    "prevScore": "7",
    "prevEle": "7",
    "lvs": [
      {
        "level": "0",
        "score": "9",
        "ele": "9",
        "span": "1"
      },
      {
        "level": "1",
        "score": "null",
        "ele": "null",
        "span": "2"
      }
    ]
  },
  {
    "score": "9",
    "ele": "9",
    "prevScore": "8",
    "prevEle": "8",
    "lvs": [
      {
        "level": "0",
        "score": "1000",
        "ele": "tail",
        "span": "1"
      }
    ]
  },
  {
    "score": "1000",
    "ele": "tail",
    "prevScore": "9",
    "prevEle": "9",
    "lvs": [
    ]
  }
]

zslCreate

创建了一个 skiplist, 初始化 length, level, header, tail 等等 

注意 header 是一个 dummy 节点, 这里 tail 初始化的并不是 header 这个 dummy 节点 

zslInsert

我们这里调试的是 "zslInsert(list, 8.2, insertAfter);", 因为这个场景稍微复杂一些, 可以更好的看到 skiplist 的相关操作 

首先是查询 8.2 这个 score 的行动路线, 存储于 update 里面, 比如这里是 header -> 5 -> 7 -> 8[倒着看, 可以基于 update, 也可以基于 rank][这里空间有限, 没有展示出 update, 可以基于 rank]

    update[5] -> 元素[5 | 5], rank[5] -> 7[元素5的偏移]

    update[4] -> 元素[5 | 5], rank[4] -> 7[元素5的偏移]

    update[3] -> 元素[7 | 7], rank[3] -> 9[元素7的偏移]

    update[2] -> 元素[7 | 7], rank[2] -> 9[元素7的偏移]

    update[1] -> 元素[8 | 8], rank[1] -> 10[元素8的偏移]

    update[0] -> 元素[8 | 8], rank[0] -> 10[元素8的偏移]

然后为新加入的元素[8.2 | insertAfter] 计算一个 level, 这里为 2 

如果新的 level > 已有的最高的 level, 更新中间数据 update[i], rank[i](辅助线) 

根据 score, element 创建数据节点 zskiplistNode 

将新元素节点 x, level 以下的节点, 更新 x 的各个层级的后继节点, span, 更新 update[i][逻辑上为改level上x的前继节点] 的后继节点为 x, span 

更新 x 的 backword 为 update[0], 更新 x.lv[0].forword 的 backword 为 x 

更新 skiplist 的 length 

# 对于结果的期望 

所以对于新加入的元素[8.2 | insertAfter], lv[0] 我们期望为元素[9 | 9], lv[1] 为 NULL, backword 为元素[8 | 8]

对于已有的元素[8 | 8], lv[0] 我们期望为元素[8.2 | insertAfter], lv[1] 为元素[8.2 | insertAfter], backword 不变 

对于已有的元素[9 | 9], lv[*] 不变, backword 更新为元素[8 | 8] 

插入新元素之后的 skiplist 如下 

整个 skiplist 所有数据如下 

[
  {
    "score": "0",
    "ele": "",
    "lvs": [
      {
        "level": "0",
        "score": "0",
        "ele": "0",
        "span": "1"
      },
      {
        "level": "1",
        "score": "0",
        "ele": "0",
        "span": "1"
      },
      {
        "level": "2",
        "score": "0",
        "ele": "head",
        "span": "2"
      },
      {
        "level": "3",
        "score": "2",
        "ele": "2",
        "span": "4"
      },
      {
        "level": "4",
        "score": "2",
        "ele": "2",
        "span": "4"
      },
      {
        "level": "5",
        "score": "5",
        "ele": "5",
        "span": "7"
      }
    ]
  },
  {
    "score": "0",
    "ele": "0",
    "lvs": [
      {
        "level": "0",
        "score": "0",
        "ele": "head",
        "span": "1"
      },
      {
        "level": "1",
        "score": "0",
        "ele": "head",
        "span": "1"
      }
    ]
  },
  {
    "score": "0",
    "ele": "head",
    "prevScore": "0",
    "prevEle": "0",
    "lvs": [
      {
        "level": "0",
        "score": "1",
        "ele": "1",
        "span": "1"
      },
      {
        "level": "1",
        "score": "1",
        "ele": "1",
        "span": "1"
      },
      {
        "level": "2",
        "score": "2",
        "ele": "2",
        "span": "2"
      }
    ]
  },
  {
    "score": "1",
    "ele": "1",
    "prevScore": "0",
    "prevEle": "head",
    "lvs": [
      {
        "level": "0",
        "score": "2",
        "ele": "2",
        "span": "1"
      },
      {
        "level": "1",
        "score": "2",
        "ele": "2",
        "span": "1"
      }
    ]
  },
  {
    "score": "2",
    "ele": "2",
    "prevScore": "1",
    "prevEle": "1",
    "lvs": [
      {
        "level": "0",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "1",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "2",
        "score": "3",
        "ele": "3",
        "span": "1"
      },
      {
        "level": "3",
        "score": "5",
        "ele": "5",
        "span": "3"
      },
      {
        "level": "4",
        "score": "5",
        "ele": "5",
        "span": "3"
      }
    ]
  },
  {
    "score": "3",
    "ele": "3",
    "prevScore": "2",
    "prevEle": "2",
    "lvs": [
      {
        "level": "0",
        "score": "4",
        "ele": "4",
        "span": "1"
      },
      {
        "level": "1",
        "score": "5",
        "ele": "5",
        "span": "2"
      },
      {
        "level": "2",
        "score": "5",
        "ele": "5",
        "span": "2"
      }
    ]
  },
  {
    "score": "4",
    "ele": "4",
    "prevScore": "3",
    "prevEle": "3",
    "lvs": [
      {
        "level": "0",
        "score": "5",
        "ele": "5",
        "span": "1"
      }
    ]
  },
  {
    "score": "5",
    "ele": "5",
    "prevScore": "4",
    "prevEle": "4",
    "lvs": [
      {
        "level": "0",
        "score": "6",
        "ele": "6",
        "span": "1"
      },
      {
        "level": "1",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "2",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "3",
        "score": "7",
        "ele": "7",
        "span": "2"
      },
      {
        "level": "4",
        "score": "null",
        "ele": "null",
        "span": "6"
      },
      {
        "level": "5",
        "score": "null",
        "ele": "null",
        "span": "6"
      }
    ]
  },
  {
    "score": "6",
    "ele": "6",
    "prevScore": "5",
    "prevEle": "5",
    "lvs": [
      {
        "level": "0",
        "score": "7",
        "ele": "7",
        "span": "1"
      }
    ]
  },
  {
    "score": "7",
    "ele": "7",
    "prevScore": "6",
    "prevEle": "6",
    "lvs": [
      {
        "level": "0",
        "score": "8",
        "ele": "8",
        "span": "1"
      },
      {
        "level": "1",
        "score": "8",
        "ele": "8",
        "span": "1"
      },
      {
        "level": "2",
        "score": "null",
        "ele": "null",
        "span": "4"
      },
      {
        "level": "3",
        "score": "null",
        "ele": "null",
        "span": "4"
      }
    ]
  },
  {
    "score": "8",
    "ele": "8",
    "prevScore": "7",
    "prevEle": "7",
    "lvs": [
      {
        "level": "0",
        "score": "8.2",
        "ele": "insertAfter",
        "span": "1"
      },
      {
        "level": "1",
        "score": "8.2",
        "ele": "insertAfter",
        "span": "1"
      }
    ]
  },
  {
    "score": "8.2",
    "ele": "insertAfter",
    "prevScore": "8",
    "prevEle": "8",
    "lvs": [
      {
        "level": "0",
        "score": "9",
        "ele": "9",
        "span": "1"
      },
      {
        "level": "1",
        "score": "null",
        "ele": "null",
        "span": "2"
      }
    ]
  },
  {
    "score": "9",
    "ele": "9",
    "prevScore": "8.2",
    "prevEle": "insertAfter",
    "lvs": [
      {
        "level": "0",
        "score": "1000",
        "ele": "tail",
        "span": "1"
      }
    ]
  },
  {
    "score": "1000",
    "ele": "tail",
    "prevScore": "9",
    "prevEle": "9",
    "lvs": [
    ]
  }
]

zslRandomLevel 

随机化的方法来计算一个 level, 限定最大 level 为 ZSKIPLIST_MAXLEVEL[默认为32] 

zslFirstInRange

首先判断真个 skiplist 里面的元素是否在 range 范围内[区间判断 head < range.min || tail > range.max] 

迭代 skiplist, 找到比 range.min 小的第一个元素 ltMin 

ltMin 向前迭代一个元素 node, 判断 node.socre 是否在 range 范围内, 如果在 返回 x 

zslLastInRange

首先判断真个 skiplist 里面的元素是否在 range 范围内[区间判断 head < range.min || tail > range.max] 

迭代 skiplist, 找到比 range.max 小的最后一个元素 ltMax 

判断 ltMax.socre 是否在 range 范围内, 如果在 返回 x 

zslFirstInLexRange

查询元素语义上在给定的 range 的第一个元素, 和 上面的 zslFirstInRange 的实现方式类似

但是 skiplist 是以 socre 第一优先级来进行的排序, 所以我的理解是这个处理 应该是存在问题的吧, 只满足 在 score 和 ele 成正比的情况下的业务查询吧 

所以 redis 的代码里面是仅仅在符合特定的场景的时候 才会使用 zslFirstInLexRange 的吗 ? 

模拟的和语义冲突的情况, 这个 skiplist 里面放入了 10 -> 0 的元素, socre 和 ele 是成反比的 

显然 range[2, 8) 再给定的 skiplist 里面是存在的, 但是最后查询出来的 firstInLexRange 和 lastInLexRange 却是为 NULL 

zslLastInLexRange

类似于 zslFirstInLexRange 的实现

zslGetRank

首先是根据 score 的比较, 初始化迭代节点为 header 

根据 level 查找, 找到该 level 中能够找小于 score 的最大的节点, 然后比较 ele 是否匹配, 如果能够匹配, 直接返回 rank 

否则 继续迭代 level -1 

所以这里是 如果 score + ele 确实是在 skiplist 中存在, 则能够计算出准确的 rank 

如果 score + ele 在 skiplist 中出现多次, 计算的是最后一对 score + ele 的 rank 

因为每一个 level 的迭代会找小于 score 的最大的节点, 之后会有一次 ele 的比较, 一次一些 score 小于 传入score, ele 能够匹配上的元素, 也能够查询到 rank 

比如如上单元测试中的 "unsigned long rankOfTem2 = zslGetRank(list, 3, sdsfromlonglong(2));", skiplist 中是没有 [3 | 2] 的元素的, 但是依然能够 查询到 rank 

zslDelete

首先是查询 8.2 这个 score 的行动路线, 存储于 update 里面, 比如这里是 header -> 5 -> 7 -> 8[倒着看, 可以基于 update] 

    update[5] -> 元素[5 | 5]

    update[4] -> 元素[5 | 5]

    update[3] -> 元素[7 | 7]

    update[2] -> 元素[7 | 7]

    update[1] -> 元素[8 | 8]

    update[0] -> 元素[8 | 8]

然后找到 待删除的节点 x, 比较 score 和 ele, 确保相同 

接着处理 删除 node 的操作  

    更新 update[i] 的 forword 为 x.forword, 更新 span 

    更新 x.forword.backword 为 x.backword 

    检查无用的 level, 更新 skiplist.length 

如果需要将 node 带回, 更新 node 引用, 否则 free node 

zslDeleteRangeByScore

根据 score 的区间删除 skiplist 中的元素 

迭代到 score 满足 range 的第一个元素, 删除该元素 x, 删除 dict 里面的 x.ele 

然后向后迭代, 如果依然在 range 范围内, 继续删除 

完 

以上是关于08 关于 skiplist的主要内容,如果未能解决你的问题,请参考以下文章

死磕 Redis----- Redis 数据结构: skiplist

死磕 Redis----- Redis 数据结构: skiplist

关于代码片段的时间复杂度

关于片段生命周期

5分钟了解Redis的内部实现跳跃表(skiplist)

浅析SkipList跳跃表原理及代码实现