暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Redis(版本4.0)日记20210809--跳表(SkipList)

无限递归 2021-08-13
212

本篇日记将记录Redis中使用到的跳跃表结构,该结构是ZSET底层encoding之一。欢迎提出意见和建议,比如文章完善度方面的,文章布局结构方面的等都可以,笔者将会持续改进

跳表数据结构?

源码中的结构声明如下:

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode { // 跳表的节点
    sds ele; // 元素是一个简单动态字符串
    double score; // 分数,用来排序,区间查询
struct zskiplistNode *backward; // 指像前一个节点的指针
struct zskiplistLevel { // 层结构,一个节点可以有多层
struct zskiplistNode *forward; // 每层都有一个指向后一个节点的指针
unsigned int span; // 跨度
} level[];
} zskiplistNode;


typedef struct zskiplist { // 跳表
    struct zskiplistNode *header, *tail; // 头、尾指针
unsigned long length; // 节点个数
    int level; // 除了header节点外所有节点中层数的最大值
} zskiplist;
复制


节点层数如何确定?
对于节点的层数是如何确定的,本日记就从源码角度进行一下解析,首先找到确定层数的相关代码:
首先我们找到了创建新skiplist的代码,可以看到其中有一段对header节点初始化层数的代码:
/* Create a new skiplist. */
zskiplist *zslCreate(void) {
int j;
zskiplist *zsl;


zsl = zmalloc(sizeof(*zsl));
zsl->level = 1;
zsl->length = 0;
    // header 头节点初始化开始
    // 确定头节点有ZSKIPLIST_MAXLEVEL层
    // ZSKIPLIST_MAXLEVEL = 32 可以在server.h中找到
    // #define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    // 然后遍历层,将每层的后继节点置为null,span置为0
for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
zsl->header->level[j].forward = NULL;
zsl->header->level[j].span = 0;
}
    // 将头节点的前躯节点置为null
zsl->header->backward = NULL;
    // header 头节点初始化结束
zsl->tail = NULL;
return zsl;
}
复制


下面在创建节点时,看到一个普通节点获取层数的方法,然后只要看下这个方法是怎么实现层数获取的就可以了:
/* 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) {
... 省略其他代码
level = zslRandomLevel();
    ... 省略其他代码
return x;
}
复制


随机获取层数的代码如下:这里使用到了一个叫做【 幂律分布】的高级方法,小编也不甚了解,感兴趣的小伙伴还请自行学习其原理,明白了也可以讲给小编听听😄
幂律分布的作用从注释中可以得出:降低高level出现的概率,即1,2,3,4,5,6...,30,31,32 这32个数,越大的数字越难出现。
幂律分布我们暂时不考虑,这里就单看redis这段代码的实现:其实就是在0~0xFFFF之间随机一个数,然后判断这个随机数是不是比0xFFFF这个数的四分之一还小,如果是,就将level++,最后在返回时如果level > 32了,就取32,如果 <= 32 就取level,即level 每次有四分之一的概率往上累加,四分之三的概率不会累加,保证了上面说的越大的数字越难出现。
/* Returns a random level for the new skiplist node we are going to create.
* The return value of this function is between 1 and ZSKIPLIST_MAXLEVEL
* (both inclusive), with a powerlaw-alike distribution where higher
* levels are less likely to be returned.
* 为即将创建的跳表节点返回一个随机的level
* 返回值将在1~ZSKIPLIST_MAXLEVEL之间(包含1和32)
* 该方法使用powerlaw-alike distribution(google翻译为:幂律分布)
* 这种概率的方式使level返回的尽量小
*/
int zslRandomLevel(void) {
int level = 1;
// ZSKIPLIST_P = 0.25 可以在server.h中找到
// #define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
// level 有四分之一的概率往上进行l累加
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
复制
文章转载自无限递归,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论