Redis zskipList总结

184 阅读6分钟

Redis SkipList

数据结构

zskipListNode

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode
{
    // sorted set中的元素
    sds ele;
    // 权重, 跳表中所有的元素都是按照score进行排列的
    double score;
    // 后向指针(为了从后往前遍历方便)
    struct zskiplistNode *backward;
    // level:每一层都有一个当前节点再本层的跨度span和前向指针
    struct zskiplistLevel
    {
        struct zskiplistNode *forward;
        // 这个跨度指的是当前节点的forward指针和自己之间的距离
        unsigned long span;
    } level[];
} zskiplistNode;

zskiplist

typedef struct zskiplist
{
    // 头指针尾指针
    struct zskiplistNode *header, *tail;
    // 跳表包含多少个节点(除去表头结点)
    unsigned long length;
    // 跳表的层数,当前最高层数
    int level;
} zskiplist;

跳表相关操作

查询


//获取跳表的表头
x = zsl->header;
//从最大层数开始逐一遍历
for (i = zsl->level-1; i >= 0; i--) {
   ...

   // 前提:在第i层,x不是尾节点
   // 条件1:x在第i层的下一个节点score小于当前传递进来的score
   // 条件2:score相同,但是下一个节点的ele sds小于 传进来的ele
   while (x->level[i].forward && (x->level[i].forward->score < score || (x->level[i].forward->score == score 
    && sdscmp(x->level[i].forward->ele,ele) < 0))) {
      ...
      // 向后移动
      x = x->level[i].forward;
    }

    ...
}

插入

插入一个节点的操作相对来说是比较复杂的,可以拆解成如下几个部分:

  • 查询并保存每一层要插入的位置信息
  • 构造新节点,插入
  • 调整前向、后向和span信息
/* 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)
{
    // update数组用来存储在本层中每一个新要插入节点的位置的前一个节点,也就是降层节点
    // x作为遍历的辅助指针
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    // rank数组来记录遍历查询节点的过程中每层跨越的长度
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));

    // 先获取跳表的header节点
    x = zsl->header;
    // 从上到下开始遍历
    for (i = zsl->level - 1; i >= 0; i--)
    {
        /* store rank that is crossed to reach the insert position */
        // 如果是最高层则rank[i] = 0
        // 否则,这里需要注意的是rank[i]起始值都等于rank[i + 1]的值,那么层层累加
        // 最后rank[0] + 1就是新节点 前置节点的排位
        rank[i] = i == (zsl->level - 1) ? 0 : rank[i + 1];
        // 1.新插入的节点的score是否大于前向节点的score
        // 2.score相同的时候判断ele大小
        while (x->level[i].forward &&
               (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                 sdscmp(x->level[i].forward->ele, ele) < 0)))
        {
            // 在查询每一层下沉节点的位置时,记录一下经过的跨度
            rank[i] += x->level[i].span;
            // 移动到forward指向的节点
            x = x->level[i].forward;
        }
        // 表示找到了每一层上的下沉节点位置(这一层中将要插入节点的前一个位置),将其更新到update数组中
        update[i] = x;
    }

    /* we assume the element is not already inside, since we allow duplicated
     * scores, reinserting the same element should never happen since the
     * caller of zslInsert() should test in the hash table if the element is
     * already inside or not. */
    // 随机生成一个层数
    level = zslRandomLevel();
    // 如果生成的层数大于现有层数,就要调整多出来的那些层的信息
    if (level > zsl->level)
    {
        for (i = zsl->level; i < level; i++)
        {
            // rank暂时置0
            rank[i] = 0;
            // 下称节点暂时为头节点
            update[i] = zsl->header;
            // 下沉节点的跨度暂时为整个链表的长度
            update[i]->level[i].span = zsl->length;
        }
        // 调整整个的链表level
        zsl->level = level;
    }

    // 创建一个新的node
    x = zslCreateNode(level, score, ele);
    // 自底向上遍历,插入节点并更新跨度
    for (i = 0; i < level; i++)
    {
        // 新节点的前向指针指向之前保存的这一层的位置(链表插入)
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here */

        // rank[0] - rank[i]代表:第i层的降层节点和要插入节点之间的跨度
        // update[i] -> level[i].span - (rank[0] - rank[i]): 第i层要插入的节点与forward之间的距离
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
        // 降层节点的跨度也要更新 + 1
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
    for (i = level; i < zsl->level; i++)
    {
        // 如果新生成的层数比跳表的最大层数要小,那么就需要更新比新层数高的那些层要插入位置前一个节点的跨度
        update[i]->level[i].span++;
    }

    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}

删除

首先, zslDelete()函数的大部分查找功能和数组都和插入函数比较类似,大致过程如下:

int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node)
{
    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 &&
                 sdscmp(x->level[i].forward->ele, ele) < 0)))
        {
            x = x->level[i].forward;
        }
        update[i] = x;
    }
    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. */
    x = x->level[0].forward;
    if (x && score == x->score && sdscmp(x->ele, ele) == 0)
    {
        zslDeleteNode(zsl, x, update);
        if (!node)
            zslFreeNode(x);
        else
            *node = x;
        return 1;
    }
    return 0; /* not found */
}

重点主要是zslDeleteNode()函数:

/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update)
{
    int i;
    for (i = 0; i < zsl->level; i++)
    {
        // 如果是要删除的节点
        if (update[i]->level[i].forward == x)
        {
            // x在本层的跨度:x到其forward节点之间的距离,把这一部分加到下沉节点的span上,并 - 1
            update[i]->level[i].span += x->level[i].span - 1;
            // 等价于p.next = p.next.next
            update[i]->level[i].forward = x->level[i].forward;
        }
        else
        {
            // 降层节点指向的forward节点不是要删除的节点,span置为-1
            update[i]->level[i].span -= 1;
        }
    }
    if (x->level[0].forward)
    {
        // 如果删除的节点的forward节点在最底层不是空,那么改变forward的后向指针,其实就是双端链表的操作
        x->level[0].forward->backward = x->backward;
    }
    else
    {
        // 否则说明是尾节点,直接改变尾节点的指向
        zsl->tail = x->backward;
    }
    while (zsl->level > 1 && zsl->header->level[zsl->level - 1].forward == NULL)
        zsl->level--;
    zsl->length--;
}

范围查询

/* Find the first node that is contained in the specified range.
 * Returns NULL when no element is contained in the range. */
zskiplistNode *zslFirstInRange(zskiplist *zsl, zrangespec *range)
{
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */
    if (!zslIsInRange(zsl, range))
        return NULL;

    x = zsl->header;
    // 从最高层开始查找
    for (i = zsl->level - 1; i >= 0; i--)
    {
        /* Go forward while *OUT* of range. */
        // 当x前向节点存在并且x的forward的节点score不在range范围内(比min小),继续向后移动
        while (x->level[i].forward &&
               !zslValueGteMin(x->level[i].forward->score, range))
            x = x->level[i].forward;
    }

    /* This is an inner range, so the next node cannot be NULL. */
    // 找到了第一个在范围内的点
    x = x->level[0].forward;
    serverAssert(x != NULL);

    /* Check if score <= max. */
    if (!zslValueLteMax(x->score, range))
        return NULL;
    return x;
}
/* Find the last node that is contained in the specified range.
 * Returns NULL when no element is contained in the range. */
zskiplistNode *zslLastInRange(zskiplist *zsl, zrangespec *range)
{
    zskiplistNode *x;
    int i;

    /* If everything is out of range, return early. */
    if (!zslIsInRange(zsl, range))
        return NULL;

    x = zsl->header;
    // 也是从最高层开始寻找
    for (i = zsl->level - 1; i >= 0; i--)
    {
        /* Go forward while *IN* range. */
        // 当这一层上x的forward节点的score小于max,沿着本层继续搜寻,否则降层
        while (x->level[i].forward &&
               zslValueLteMax(x->level[i].forward->score, range))
            x = x->level[i].forward;
    }

    /* This is an inner range, so this node cannot be NULL. */
    serverAssert(x != NULL);

    /* Check if score >= min. */
    if (!zslValueGteMin(x->score, range))
        return NULL;
    return x;
}