八: redis的sortset数据类型常见命令、内部编码、场景

有序保存多个不重复的字符串的集合。

常见命令

zadd

  • 解释
    有序集合插入元素以及元素的分数(分数支持双精度浮点数)。
    如果本次插入的元素已经存在,则更新元素的分数,并且重排序。
    如果键不存在,则创建Key。
    如果其中元素的分数相同,则按照字典序排序。
  • 用法 zadd key [NX|XX] [CH][INCR] socre member [score member]
    NX: 仅仅更新元素的分数,元素必须存在才能操作成功
    XX: 仅仅添加元素及其分数,当元素存在,则不操作
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"1"
3)"two"
4)"2"
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zadd",zaddCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}
}
  1. 赋值要设置元素的分数给变量scoreval
  2. 在dict中查找键是否存在
  3. 键不存在,创建数据类型为zset的键其内部编码为跳表,加入键到dict中
  4. 键存在,如果数据类型不是zset返回错误信息
  5. 如果命令为zadd,则incr=0;命令为zincrby,则incr=1,则进入下面代码块
    5.1 如果元素存在,新的score等于老值加本次增加的score
    5.2 如果score的值不是数字类型,返回错误信息
  6. 如果元素不存在,新插入元素
  7. 如果元素存在,删除元素重新插入元素及其分数
/**
**  t_zset.c
**
**/
void zaddCommand(redisClient *c) {
    double scoreval;
	// 1.赋值要设置元素的分数给变量scoreval
    if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
}


void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) {
    robj *zsetobj;
    zset *zs;
    zskiplistNode *znode;
	
	//2. 在dict中查找键是否存在
    zsetobj = lookupKeyWrite(c->db,key);
    if (zsetobj == NULL) {
		//3. 键不存在,创建数据类型为zset的键其内部编码为跳表,加入键到dict中
        zsetobj = createZsetObject();
        dbAdd(c->db,key,zsetobj);
    } else {
		//4. 键存在,如果数据类型不是zset返回错误信息
        if (zsetobj->type != REDIS_ZSET) {
            addReply(c,shared.wrongtypeerr);
            return;
        }
    }
    zs = zsetobj->ptr;

    // 5. 如果命令为zadd,则incr=0;命令为zincrby,则incr=1,则进入下面代码块
    if (incr) {

        dictEntry *de = dictFind(zs->dict,ele);
		// 5.1 如果元素存在,新的score等于老值加本次增加的score
        if (de != NULL)
            score += *(double*)dictGetEntryVal(de);
		//5.2 如果score的值不是数字类型,返回错误信息
        if (isnan(score)) {
            addReplyError(c,"resulting score is not a number (NaN)");    
            return;
        }
    }

   //6. 如果元素不存在,新插入元素
    if (dictAdd(zs->dict,ele,NULL) == DICT_OK) {
        dictEntry *de;

       
        incrRefCount(ele); /* added to hash */
        znode = zslInsert(zs->zsl,score,ele);
        incrRefCount(ele); /* added to skiplist */

        /* Update the score in the dict entry */
        de = dictFind(zs->dict,ele);
        redisAssert(de != NULL);
        dictGetEntryVal(de) = &znode->score;
        touchWatchedKey(c->db,c->argv[1]);
        server.dirty++;
        if (incr)
            addReplyDouble(c,score);
        else
            addReply(c,shared.cone);
    } else {
	//7. 如果元素存在,删除元素重新插入元素及其分数
        dictEntry *de;
        robj *curobj;
        double *curscore;
        int deleted;

        /* Update score */
        de = dictFind(zs->dict,ele);
        redisAssert(de != NULL);
        curobj = dictGetEntryKey(de);
        curscore = dictGetEntryVal(de);

        /* When the score is updated, reuse the existing string object to
         * prevent extra alloc/dealloc of strings on ZINCRBY. */
        if (score != *curscore) {
            deleted = zslDelete(zs->zsl,*curscore,curobj);
            redisAssert(deleted != 0);
            znode = zslInsert(zs->zsl,score,curobj);
            incrRefCount(curobj);

            /* Update the score in the current dict entry */
            dictGetEntryVal(de) = &znode->score;
            touchWatchedKey(c->db,c->argv[1]);
            server.dirty++;
        }
        if (incr)
            addReplyDouble(c,score);
        else
            addReply(c,shared.czero);
    }
}

zcard

  • 解释
    返回有序集合元素的个数,如果键不存在,返回0
  • 用法 zcard key
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zcard key1 
(integer) 2
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"zcard",zcardCommand,2,0,NULL,1,1,1}
}
void zcardCommand(redisClient *c) {
    robj *o;
    zset *zs;
	//1.检查键是否存在以及是否数据类型是sort set,否则返回0
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,REDIS_ZSET)) return;
	//2. 获取到键中元素的指针,获取长度
    zs = o->ptr;
    addReplyLongLong(c,zs->zsl->length);
}

zcount

  • 解释
    返回有序集合中元素分数在min和max中间的元素个数。
    min max 表示在min,max之间包含min,max,
    (min (max表示在min,max之间,不包含min,max
    min最小值是-inf,max最大值是+inf
  • 用法 zcount key min max
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zcount key1 1 2
(integer)2
127.0.0.1:6379> zcount key1 -inf +inf
(integer)2
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
   {"zcount",zcountCommand,4,0,NULL,1,1,1}
}
  1. 解析min.max的值
  2. ZRANGEBYSCORE, ZREVRANGEBYSCORE命令可能是有超过四个参数的,需要解析出来
  3. 检查键是否存在以及是否数据类型是sort set,否则返回0
  4. zset的对象
  5. 获取跳表中等于min或者大于min的分数元素指针
  6. reverse=0 分数从低到高,reverse=1 分数从高到低
  7. 没有范围内的元素,直接返回
  8. zcount命令 limit = -1,offset = 0 ln表示等于或大于start的元素的指针,根据正反序,以及是否包含stop进行判断
  9. 元素在范围内计数
/**
** t_zset.c
**/

void zcountCommand(redisClient *c) {
    genericZrangebyscoreCommand(c,0,1);
}


void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
    zrangespec range;
    robj *o, *emptyreply;
    zset *zsetobj;
    zskiplist *zsl;
    zskiplistNode *ln;
    int offset = 0, limit = -1;
    int withscores = 0;
    unsigned long rangelen = 0;
    void *replylen = NULL;

    // 1.解析min.max的值
    if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
        addReplyError(c,"min or max is not a double");
        return;
    }

    //2. ZRANGEBYSCORE, ZREVRANGEBYSCORE命令可能是有超过四个参数的,需要解析出来
    if (c->argc > 4) {
        int remaining = c->argc - 4;
        int pos = 4;

        while (remaining) {
            if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
                pos++; remaining--;
                withscores = 1;
            } else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
                offset = atoi(c->argv[pos+1]->ptr);
                limit = atoi(c->argv[pos+2]->ptr);
                pos += 3; remaining -= 3;
            } else {
                addReply(c,shared.syntaxerr);
                return;
            }
        }
    }

    //3. 检查键是否存在以及是否数据类型是sort set,否则返回0
    emptyreply = justcount ? shared.czero : shared.emptymultibulk;
    if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL ||
        checkType(c,o,REDIS_ZSET)) return;
	//4. zset的对象
    zsetobj = o->ptr;
    zsl = zsetobj->zsl;

    //5. 获取跳表中等于min或者大于min的分数元素指针
    ln = zslFirstWithScore(zsl,range.min);
	//6. reverse=0 分数从低到高,reverse=1 分数从高到低
    if (reverse) {
        /* If range.min is out of range, ln will be NULL and we need to use
         * the tail of the skiplist as first node of the range. */
        if (ln == NULL) ln = zsl->tail;

        /* zslFirstWithScore returns the first element with where with
         * score >= range.min, so backtrack to make sure the element we use
         * here has score <= range.min. */
        while (ln && ln->score > range.min) ln = ln->backward;

        /* Move to the right element according to the range spec. */
        if (range.minex) {
            /* Find last element with score < range.min */
            while (ln && ln->score == range.min) ln = ln->backward;
        } else {
            /* Find last element with score <= range.min */
            while (ln && ln->level[0].forward &&
                         ln->level[0].forward->score == range.min)
                ln = ln->level[0].forward;
        }
    } else {
        if (range.minex) {
            /* Find first element with score > range.min */
            while (ln && ln->score == range.min) ln = ln->level[0].forward;
        }
    }

   //7. 没有范围内的元素,直接返回
    if (ln == NULL) {
        addReply(c,emptyreply);
        return;
    }

    /* We don't know in advance how many matching elements there
     * are in the list, so we push this object that will represent
     * the multi-bulk length in the output buffer, and will "fix"
     * it later */
    if (!justcount)
        replylen = addDeferredMultiBulkLength(c);

    /* If there is an offset, just traverse the number of elements without
     * checking the score because that is done in the next loop. */
    while(ln && offset--) {
        if (reverse)
            ln = ln->backward;
        else
            ln = ln->level[0].forward;
    }

	//8. zcount命令 limit = -1,offset = 0  ln表示等于或大于start的元素的指针
	// 根据正反序,以及是否包含stop进行判断	
    while (ln && limit--) {
        /* Check if this this element is in range. */
        if (reverse) {
            if (range.maxex) {
                /* Element should have score > range.max */
                if (ln->score <= range.max) break;
            } else {
                /* Element should have score >= range.max */
                if (ln->score < range.max) break;
            }
        } else {
            if (range.maxex) {
                /* Element should have score < range.max */
                if (ln->score >= range.max) break;
            } else {
                /* Element should have score <= range.max */
                if (ln->score > range.max) break;
            }
        }

        //9. 元素在范围内计数
        rangelen++;
        if (!justcount) {
            addReplyBulk(c,ln->obj);
            if (withscores)
                addReplyDouble(c,ln->score);
        }
		
        if (reverse)
            ln = ln->backward;
        else
            ln = ln->level[0].forward;
    }

    if (justcount) {
        addReplyLongLong(c,(long)rangelen);
    } else {
        setDeferredMultiBulkLength(c,replylen,
             withscores ? (rangelen*2) : rangelen);
    }
}

zincrby

  • 解释
    有序集合指定元素增加指定的分数,结果返回元素最终的值;
    如果元素不存在,则元素的分数为本次指定的值;
    如果键不存在,创建键后,增加元素并且指定其值为本次指定的值;
    如果键的类型不是有序集合(zset),则返回错误。
  • 用法 zincrby key increment member
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zincrby key1 3 one
(integer)4
127.0.0.1:6379> zincrby key1 5 three
(integer)5
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"4"
3)"three"
4)"5"
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zincrby",zincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}
}



/**
** t_zset.c 参考zadd
**/
void zincrbyCommand(redisClient *c) {
    double scoreval;
    if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
    c->argv[3] = tryObjectEncoding(c->argv[3]);
    zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
}

zlexcount

  • 解释
    当有序集合的所有元素的分数是相同的时候,会强制使用字典序对元素进行排序。
    这个元素返回在start,stop之间的的元素个数。
  • 用法 zlexcount key min max
    min,max的书写必须使用(,[, ( 表示不包含, [ 表示包含。
    zlexcount - + 表示所有元素.
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer)
127.0.0.1:6379> zadd key1 1 two
(integer)1
127.0.0.1:6379> zlexcount key1 - +
(integer)2
127.0.0.1:6379> zlexcount key1 [o  [w
(integer)2

zpopmax

  • 解释
    返回有序集合中分数最大的count的元素,并且从有序集合中删除这些元素
    如果count未指定,则操作分数最大的元素。
    注意:这个命令redis5.0.0及之后的版本才支持
  • 用法 zpopmax key [count]
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zpopmax key1
1)"two"
2)"2"

zpopmin

  • 解释
    返回有序集合中分数最小的count的元素,并且从有序集合中删除这些元素
    如果count未指定,则操作分数最小的元素。
    注意:这个命令redis5.0.0及之后的版本才支持
  • 用法
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zpopmix key1
1)"one"
2)"1"
  • 源码

zrange

  • 解释
    返回有序集合的元素,按照分数从低到高返回。
    如果元素的分数相同,按照元素的字典序排序;
    如果想返回元素的分数,使用withscores;
    start,stop从0开始(表示第一个元素),start,stop包含start,stop,
    如zrange key 0 1 返回索引为0,1之间的元素包含0,1;
  • 用法 zrange key start stop [witscores]
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"1"
3)"two"
4)"2"
  • 源码
  1. 解析出statr,stop的值
  2. 解析传入的命令参数是否包含withscores需要返回元素对应的分数
  3. 如果命令的参数大于5,返回语法错误
  4. 查询键是否存在以及是否是sortset数据类型
  5. 转换存储的负的索引值
  6. 如果startd大于end或start超出了元素的个数返回错误信息
  7. 如果stop最大为元素个数的长度
  8. 获取到开始遍历的元素对象
  9. 返回元素,如果要返回分数也返回分数
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zrange",zrangeCommand,-4,0,NULL,1,1,1}
}
/**
** t_zset.c
**/

void zrangeCommand(redisClient *c) {
    zrangeGenericCommand(c,0);
}

void zrangeGenericCommand(redisClient *c, int reverse) {
    robj *o;
    long start;
    long end;
    int withscores = 0;
    int llen;
    int rangelen, j;
    zset *zsetobj;
    zskiplist *zsl;
    zskiplistNode *ln;
    robj *ele;

	//1. 解析出statr,stop的值
    if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
        (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;

	//2. 解析传入的命令参数是否包含withscores需要返回元素对应的分数
    if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
        withscores = 1;
    } else if (c->argc >= 5) {
	//3. 如果命令的参数大于5,返回语法错误
        addReply(c,shared.syntaxerr);
        return;
    }
	//4. 查询键是否存在以及是否是sortset数据类型
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
         || checkType(c,o,REDIS_ZSET)) return;
    zsetobj = o->ptr;
    zsl = zsetobj->zsl;
    llen = zsl->length;

    //5. 转换存储的负的索引值
    if (start < 0) start = llen+start;
    if (end < 0) end = llen+end;
    if (start < 0) start = 0;

    //6. 如果startd大于end或start超出了元素的个数返回错误信息
    if (start > end || start >= llen) {
        addReply(c,shared.emptymultibulk);
        return;
    }
	//7. 如果stop最大为元素个数的长度
    if (end >= llen) end = llen-1;
    rangelen = (end-start)+1;

    //8. 获取到开始遍历的元素对象
    if (reverse) {
        ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start);
    } else {
        ln = start == 0 ?
            zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
    }

    //9. 返回元素,如果要返回分数也返回分数
    addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
    for (j = 0; j < rangelen; j++) {
        ele = ln->obj;
        addReplyBulk(c,ele);
        if (withscores)
            addReplyDouble(c,ln->score);
        ln = reverse ? ln->backward : ln->level[0].forward;
    }
}

zrevrange

  • 解释
    返回有序集合的元素,按照分数从高到低返回。
    如果元素的分数相同,按照元素的字典序排序;
    如果想返回元素的分数,使用withscores;
    start,stop从0开始(表示第一个元素),start,stop包含start,stop,
    zrevrange key 0 1 返回索引为0,1之间的元素包含0,1;
  • 用法 zrevrange key start stop [withscores]
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrevrange key1 0 -1 withscores
1)"two"
2)"2"
3)"one"
4)"1"
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zrevrange",zrevrangeCommand,-4,0,NULL,1,1,1}
}

/**
** 参见zrange
** t_zset.c
**/
void zrevrangeCommand(redisClient *c) {
    zrangeGenericCommand(c,1);
}

zrangebyscore

  • 解释

返回有序集合中的元素分数在start,stop(包含start,stop)之间按照分数从低到高排序,
如果分数相同,按照字典序排序,

  • 用法 zrangebyscore key min max [withscores] [limit offset count]
    withscores: 返回包含元素的分数
    min:最小是-inf
    max:最大是+inf
    min, max = -inf, +inf 表示返回所有的元素
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrangebyscore key1 -inf +inf
1)"one"
2)"two"
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}
}

/**
** 参见zcount
** t_zset.c
**/
void zrangebyscoreCommand(redisClient *c) {
    genericZrangebyscoreCommand(c,0,0);
}

zrank

  • 解释

返回有序集合中指定元素的索引,
索引从0开始,元素排序按照分数从低到高。
zrevrank按照分数从高到低排序。

  • 用法 zrank key member
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zrank key1 two
1)1
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}
}


1.查找键是否存在并且是否是sortset数据类型
2.查找对应的元素
3.元素不存在返回空
4.查询元素的分数,以及其分数的索引


/**
** 
** t_zset.c
**/

void zrankCommand(redisClient *c) {
    zrankGenericCommand(c, 0);
}


void zrankGenericCommand(redisClient *c, int reverse) {
    robj *o;
    zset *zs;
    zskiplist *zsl;
    dictEntry *de;
    unsigned long rank;
    double *score;
	//1.查找键是否存在并且是否是sortset数据类型
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,o,REDIS_ZSET)) return;

    zs = o->ptr;
    zsl = zs->zsl;
	//2. 查找对应的元素
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    de = dictFind(zs->dict,c->argv[2]);
	//3. 元素不存在返回空
    if (!de) {
        addReply(c,shared.nullbulk);
        return;
    }
	//4. 查询元素的分数,以及其分数的索引
    score = dictGetEntryVal(de);
    rank = zslGetRank(zsl, *score, c->argv[2]);
    if (rank) {
        if (reverse) {
            addReplyLongLong(c, zsl->length - rank);
        } else {
            addReplyLongLong(c, rank-1);
        }
    } else {
        addReply(c,shared.nullbulk);
    }
}

zrem

  • 解释

从有序集合中删除指定元素,如果元素不存在,则忽略。
返回值:返回成功删除元素的给个数,如果元素不存在,返回0

  • 用法 zrem key member [member]
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zrank key1 two
1)1
127.0.0.1:6379> zrem key1 one
(integer)1
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zrem",zremCommand,3,0,NULL,1,1,1}
}

1.查找键是否存在并且是否是sortset数据类型	
2. 查找元素的对象,如果为null,返回0
3. 从skiplist中删除

void zremCommand(redisClient *c) {
    robj *zsetobj;
    zset *zs;
    dictEntry *de;
    double curscore;
    int deleted;
	//1.查找键是否存在并且是否是sortset数据类型	
    if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,zsetobj,REDIS_ZSET)) return;

    zs = zsetobj->ptr;
    c->argv[2] = tryObjectEncoding(c->argv[2]);
	//2. 查找元素的对象,如果为null,返回0
    de = dictFind(zs->dict,c->argv[2]);
    if (de == NULL) {
        addReply(c,shared.czero);
        return;
    }
    //3. 从skiplist中删除
    curscore = *(double*)dictGetEntryVal(de);
    deleted = zslDelete(zs->zsl,curscore,c->argv[2]);
    redisAssert(deleted != 0);

    /* Delete from the hash table */
    dictDelete(zs->dict,c->argv[2]);
    if (htNeedsResize(zs->dict)) dictResize(zs->dict);
    if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
    touchWatchedKey(c->db,c->argv[1]);
    server.dirty++;
    addReply(c,shared.cone);
}

zscore

  • 解释
    返回元素的分数,如果键或者元素不存在,返回nil
  • 用法 zscore key member
  • 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zscore key1 two
"3"
  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"zscore",zscoreCommand,3,0,NULL,1,1,1}
}

1.查找键是否存在并且是否是sortset数据类型	
2. 查询元素对应的对象
3. 如果元素不存在返回null
4.元素存在,返回其分数

void zscoreCommand(redisClient *c) {
    robj *o;
    zset *zs;
    dictEntry *de;
	//1.查找键是否存在并且是否是sortset数据类型	
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,o,REDIS_ZSET)) return;

    zs = o->ptr;
    c->argv[2] = tryObjectEncoding(c->argv[2]);
	//2. 查询元素对应的对象
    de = dictFind(zs->dict,c->argv[2]);
    if (!de) {
	//3. 如果元素不存在返回null
        addReply(c,shared.nullbulk);
    } else {
	//4.元素存在,返回其分数
        double *score = dictGetEntryVal(de);

        addReplyDouble(c,*score);
    }
}

内部编码

skiplist 跳表

使用场景

场景一: 排行榜

用户为爱豆打榜,member存爱豆的id,用户每次打榜操作,增加爱豆id对应的score

场景二: 热搜话题热度

每个话题用户搜索,发布话题内容,对应的话题id的分数增加一定值。
每次返回前十个分数最高的话题