redis sort set4

「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。

Sorted-Set底层存储数据结构

3.4.2 查到插入的位置

我们假如跳表现在已经有元素1、11、23三个元素了,我们想插入一个新元素21,如图3.3所示:

sort-set-init

我们先看一下找到插入位置的源代码:

zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
unsigned int rank[ZSKIPLIST_MAXLEVEL];
int i, level;

serverAssert(!isnan(score));
x = zsl->header;
for (i = zsl->level-1; i >= 0; i--) {
  /* store rank that is crossed to reach the insert position */
  rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
  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;
    x = x->level[i].forward;
  }
  update[i] = x;
}
复制代码

查找节点(score=21,level=2)的插入位置,逻辑如下: 1)第一次for循环,i=1。x现在为跳跃表的头节点; 2)现在i的值与zsl->level-1相等,所以rank[1]*的值为*0; 3)header->level[1].forward存在,并且header->level[1].forward->score(1)*小于要插入值的*score,所以while循环可以进入,rank[1]=1,x赋值为第一个节点; 4)第一个节点的第1层的forward指向NULL,所以while循环不会再进入,经过第一次for循环,rank[1]=1,**x和update[1]**都为第一个节点(score=1)。

sort-insert-first

5)for循环进入第二次,i=0。x为跳跃表第一个节点(score=1) 6)现在i的值与zsl->level-1不相等,所以rank[0]**等于**rank[1]*的值赋值为*1; 6)x->level[0]->forward存在,并且x->level[0].foreard->score(11)小于要插入的score,所以while循环可以进入,*rank[0]=2*,x为第二个节点(score=11)。 7)**x->level[0]->forward**存在,并且x->level[0].foreard->score(23)**大于要插入的score,所以while不会再进入,经过第二次for循环,rank[0]=2x和update[0]**都为第二个节点(score=11),如**图3.5所示:

sort-insert-first

这个时候我们就找到插入位置在元素11和元素23中间。

3.4.3 调整跳表的高度

level = zslRandomLevel();
if (level > zsl->level) {
  for (i = zsl->level; i < level; i++) {
    rank[i] = 0;
    update[i] = zsl->header;
    update[i]->level[i].span = zsl->length;
  }
  zsl->level = level;
}
复制代码

假如我们生成的高度是3,这个时候我们需要调整一下整个跳表的高度,此时i的值是2level的值是3,所以我们只能进入一次循环,此时rank[2] = 0update[2]**指向头结点,**update[2]->level[2].span = 3**,**zsl->level = 3**,调整完高度,如**图3.6所示:

sort-insert-first

3.4.4 插入元素

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 */
    x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
    update[i]->level[i].span = (rank[0] - rank[i]) + 1;
}

for (i = level; i < zsl->level; i++) {
  update[i]->level[i].span++;
}
复制代码

level的值为3,所以for循环可以执行三遍,插入过程如下:

for循环第一遍

1)xlevel[0]**的**forwardupdate[0]**的**level[0]**的**forward节点,即x->level[0].forward为score=23的节点;

2)**update[0]level[0]**的下个节点为新插入的节点;

3)**rank[0]*的值为0,**update[0]->level[0].span=1**,所以*x->level[0].span=1、update[0]->level[0].span=1

我们再看一下经过第一次for循环,我们的结构,如图3.7所示:

sort-insert-four

for循环第二遍: 1)xlevel[1]**的**forwardupdate[1]**的**level[1]**的forward节点,即**x->level[1].forward为NULL; 2)**update[1]level[1]**的下个节点为新插入的节点; 3)**rank[1]*的值为1,**update[1]->level[1].span**等于2,所以*x->level[1].span=1 4)update[1]->level[1].span=2

我们再看一下经过第二次for循环,我们的结构,如图3.8所示:

sort-insert-five

for循环第三遍: 1)xlevel[2]**的forward为**update[2]**的**level[2]**的forward节点,即**x->level[2].forward为NULL; 2)update[2]**的**level[2]*的下个节点为新插入的节点; 3)**rank[2]**的值为2,因为**update[2]->level[2].span**等于跳跃表的总长度*3,所以x->level[2].span=1**; 4)update[2]->level[2].span=3

我们再看一下经过第三次for循环,我们的结构,如图3.9所示:

sort-insert-six

至此,我们的插入过程完成。

3.4.5 调整backward并更新跳表的长度

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++;
复制代码

x->backward的值等于,判断update[0] == zsl->header是否相等,如果想当代表是个空跳表,则赋值为NULL,如果不是的话,就把update[0]**的赋给它,也就是说指向**score = 11的backward。

第二步判断是否是尾结点,如果不是则调整对应的backward,如果是就更新跳表的尾部指向。

最后再更新跳表的length = 4,调整完的结构如图3.10所示:

sort-insert-seven

好了,我们的插入元素整个源码分析到此告一段落,本小节通过分析插入元素21的过程带大家分析每一句源码的涵义,希望对大家了解插入过程有所帮助。

3.5 Sorted-Set查询一个元素源码分析

下面通过zslGetRank函数来看一下查询一个元素过程,下面是具体的源代码:

unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *x;
    unsigned long rank = 0;
    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))) {
            rank += x->level[i].span;
            x = x->level[i].forward;
        }

        /* x might be equal to zsl->header, so test if obj is non-NULL */
      	//如果查找到相同的元素,则直接返回,rank
        if (x->ele && sdscmp(x->ele,ele) == 0) {
            return rank;
        }
    }
  	
  	//否则返回0
    return 0;
}
复制代码

我们可以通过源代码来看,其实查找一个元素跟我们插入过程中查找插入位置的代码基本一致,具体查询过程此小节就不详细展开了,(可以查看插入过程查找插入位置的详细讲解)。

3.6 Sorted-Set查询多个元素源码分析

3.6.1 整体源码分析

查询多个元素我们通过zrangezrevrange的命令的实现来进行源码分析:

void zrangeGenericCommand(client *c, int reverse) {
    robj *key = c->argv[1];
    robj *zobj;
    int withscores = 0;
    long start;
    long end;
    long llen;
    long rangelen;
		
  	//中间省略一下无用的代码
    .....

   	//获取跳表当前的长度,请参数小节3.9.1
    llen = zsetLength(zobj);
  
  	//初始化一下开始游标和结束游标
    if (start < 0) start = llen+start;
    if (end < 0) end = llen+end;
    if (start < 0) start = 0;

    /* Invariant: start >= 0, so this test will be true when end < 0.
     * The range is empty when start > end or start >= length. */
  	//异常判断
    if (start > end || start >= llen) {
        addReply(c,shared.emptymultibulk);
        return;
    }
  
  	//得到获取元素的个数
    if (end >= llen) end = llen-1;
    rangelen = (end-start)+1;

    /* Return the result in form of a multi-bulk reply */
    addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen);
		
  	//如果是压缩列表,这次我们不关心压测列表,可以先跳过这段代码
    if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
        unsigned char *zl = zobj->ptr;
        unsigned char *eptr, *sptr;
        unsigned char *vstr;
        unsigned int vlen;
        long long vlong;

        if (reverse)
            eptr = ziplistIndex(zl,-2-(2*start));
        else
            eptr = ziplistIndex(zl,2*start);

        serverAssertWithInfo(c,zobj,eptr != NULL);
        sptr = ziplistNext(zl,eptr);

        while (rangelen--) {
            serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
            serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
            if (vstr == NULL)
                addReplyBulkLongLong(c,vlong);
            else
                addReplyBulkCBuffer(c,vstr,vlen);

            if (withscores)
                addReplyDouble(c,zzlGetScore(sptr));

            if (reverse)
                zzlPrev(zl,&eptr,&sptr);
            else
                zzlNext(zl,&eptr,&sptr);
        }
		//如果是跳表
    } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
        zset *zs = zobj->ptr;
        zskiplist *zsl = zs->zsl;
        zskiplistNode *ln;
        sds ele;

        /* Check if starting point is trivial, before doing log(N) lookup. */
      	//如果逆序,则从后往前取
        if (reverse) {
            ln = zsl->tail;
          	//如果start不是从0开始,则先要找到start的那个起点的结点
            if (start > 0)
              	//通过rank获取该结点,请参考3.9.2
                ln = zslGetElementByRank(zsl,llen-start);
        } else {
          	//从第一个结点开始
            ln = zsl->header->level[0].forward;
            if (start > 0)
                ln = zslGetElementByRank(zsl,start+1);
        }
				
      	//循环这次去除取数据的个数,直到元素取完
        while(rangelen--) {
          	//断言
            serverAssertWithInfo(c,zobj,ln != NULL);
            ele = ln->ele;
          	//把数据写到对client的缓冲区中,批量返回请参考3.9.3
            addReplyBulkCBuffer(c,ele,sdslen(ele));
          	
          	//假如带了withscores参数
            if (withscores)
              	//把结点的score(double类型)也同时写到缓冲区,请参考3.9.4
                addReplyDouble(c,ln->score);
          
          	//下一个结点,如果从头那,则用forward找到下一个结点,如果是从尾部拿,则用backward找打前一个结点
            ln = reverse ? ln->backward : ln->level[0].forward;
        }
    } else {
        serverPanic("Unknown sorted set encoding");
    }
}
复制代码

这个过程就非常简单了,我们通过两张图来分别描述一下从头取元素,从尾取元素的过程。

3.6.2 Zrange查询过程

命令:zrange mytest 0 -1**,从头部取元素的过程,1 -> 11 -> 21 -> 23 结束。如图3.12

redis-zrange

3.6.3 Zrevrange查询过程

命令:zrevrange mytest 0 -1**,从尾部取元素的过程,23 -> 21 -> 11 -> 1 结束。如图3.13

redis-zrange

猜你喜欢

转载自juejin.im/post/7068650946854649887
今日推荐