前言

关于 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个指针]

08 关于 skiplist_skiplist

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

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

08 关于 skiplist_redis_02

整个 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 节点 

08 关于 skiplist_数据_03

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] 

08 关于 skiplist_skiplist_04

08 关于 skiplist_skiplist_05

插入新元素之后的 skiplist 如下 

08 关于 skiplist_redis_06

整个 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] 

08 关于 skiplist_数据结构_07

zslFirstInRange

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

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

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

08 关于 skiplist_redis_08

zslLastInRange

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

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

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

08 关于 skiplist_数据_09

zslFirstInLexRange

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

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

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

08 关于 skiplist_数据_10

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

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

08 关于 skiplist_数据_11

zslLastInLexRange

类似于 zslFirstInLexRange 的实现

08 关于 skiplist_redis_12

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 

08 关于 skiplist_数据结构_13

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 

08 关于 skiplist_skiplist_14

08 关于 skiplist_redis_15

zslDeleteRangeByScore

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

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

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

08 关于 skiplist_redis_16

完