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