Redis使用及源码剖析-17.Redis排序-2021-2-3


一、排序命令

Redis 的sort命令可以对列表键、集合键或者有序集合键的值进行排序,如下所示:

//列表排序
redis>rpush nums 3 1 2
redis>lrange nums 0 -1
3 1 2
redis>sort nums
1 2 3 
//集合排序,alpha表示按照字母顺序排序
redis>sadd str aaa bbb ccc
redis>smembers str
bbb aaa ccc //集合元素是无序的
redis>sort str alpha
aaa bbb ccc //排序后的集合元素
//sort by使用
redis>zadd grade 99 wyl 97 wyq 96 sjx
redis>zrange grade 0 -1
sjx wyq wyl
//为每个学生设置序号
redis>mset wyl_id 1 sjx_id 2 wyq_id 3
redis>sort grade by *_id
wyl sjx wyq //此时变为以id排序了

二、命令实现

1.排序对象定义

redis专门定义了一个排序结构体redisSortObject 用来排序,定义如下:

typedef struct _redisSortObject {

    // 被排序键的值
    robj *obj;

    // 权重
    union {

        // 排序数字值时使用
        double score;

        // 排序带有 BY 选项的字符串值时使用
        robj *cmpobj;

    } u;

} redisSortObject;

2.sort key命令实现

sort key命令可以对一个包含数值的key键直接进行排序,如服务器执行(一、命令实现)SORT nums 命令的详细步骤如下:
a.创建一个和 numbers 列表长度相同的数组, 该数组的每个项都是一个 redis.h/redisSortObject 结构, 如下图所示:
在这里插入图片描述
b.遍历数组, 将各个数组项的 obj 指针分别指向 numbers 列表的各个项, 构成 obj 指针和列表项之间的一对一关系, 如下图所示:
在这里插入图片描述
c.遍历数组, 将各个 obj 指针所指向的列表项转换成一个 double 类型的浮点数, 并将这个浮点数保存在相应数组项的 u.score 属性里面, 如下图所示:
在这里插入图片描述
d.根据数组项 u.score 属性的值, 对数组进行数字值排序, 排序后的数组项按 u.score 属性的值从小到大排列, 如下图所示:
在这里插入图片描述
d.遍历数组, 将各个数组项的 obj 指针所指向的列表项作为排序结果返回给客户端: 程序首先访问数组的索引 0 , 返回 u.score 值为 1.0 的列表项 “1” ; 然后访问数组的索引 1 , 返回 u.score 值为 2.0 的列表项 “2” ; 最后访问数组的索引 2 , 返回 u.score 值为 3.0 的列表项 “3”

3.alpha选项实现

sort key alpha可以对key的值按照字符串顺序排序,如(一、命令实现)sort str alpha实现如下:
a.创建一个和 str 列表长度相同的数组, 该数组的每个项都是一个 redis.h/redisSortObject 结构。
b.遍历数组, 将各个数组项的 obj 指针分别指向 str集合的各个项, 构成 obj 指针和集合元素之间的一对一关系。
c.根据obj指针指向的集合元素, 对数组进行字符顺序排序, 排序后的数组项按 集合元素的字符串顺序从小到大排列
d.遍历数组, 将各个数组项的 obj 指针所指向的集合元素作为排序结果返回给客户端。

4.by选项实现

默认情况下sort命令使用被排序的键的值进行排序,但是使用by选项后,可以让它按照其他字符串键的值进行排序。如(一、命令实现)sort grade by _id实现如下:
a.创建一个和 grade 列表长度相同的数组, 该数组的每个项都是一个 redis.h/redisSortObject 结构。
b.遍历数组, 将各个数组项的 obj 指针分别指向 grade 集合的各个项, 构成 obj 指针和集合元素之间的一对一关系。
c.遍历数组,根据obj指向的的集合元素,以及by指定的
-id,查找对应权重键的值。如集合元素为sjx,则查找sjx-id的值,等于3。
d.将查找的权重键的值转换成double类型的浮点数,然后保存在对应数组项的u.score属性中。
f.遍历数组, 将各个数组项的 obj 指针所指向的集合元素作为排序结果返回给客户端。

三、排序源码

redis排序相关代码均位于sort.c中,sort使用的排序函数如下:

/* sortCompare() is used by qsort in sortCommand(). Given that qsort_r with
 * the additional parameter is not standard but a BSD-specific we have to
 * pass sorting parameters via the global 'server' structure */
// 排序算法所使用的对比函数
int sortCompare(const void *s1, const void *s2) {
    const redisSortObject *so1 = s1, *so2 = s2;
    int cmp;

    if (!server.sort_alpha) {

        /* Numeric sorting. Here it's trivial as we precomputed scores */
		// 数值排序

        if (so1->u.score > so2->u.score) {
            cmp = 1;
        } else if (so1->u.score < so2->u.score) {
            cmp = -1;
        } else {
            /* Objects have the same score, but we don't want the comparison
             * to be undefined, so we compare objects lexicographically.
             * This way the result of SORT is deterministic. */
			// 两个元素的分值一样,但为了让排序的结果是确定性的(deterministic)
			// 我们对元素的字符串本身进行字典序排序
            cmp = compareStringObjects(so1->obj,so2->obj);
        }
    } else {

        /* Alphanumeric sorting */
		// 字符排序

        if (server.sort_bypattern) {

		    // 以模式进行对比

			// 有至少一个对象为 NULL
            if (!so1->u.cmpobj || !so2->u.cmpobj) {
                /* At least one compare object is NULL */
                if (so1->u.cmpobj == so2->u.cmpobj)
                    cmp = 0;
                else if (so1->u.cmpobj == NULL)
                    cmp = -1;
                else
                    cmp = 1;
            } else {
                /* We have both the objects, compare them. */
				// 两个对象都不为 NULL

                if (server.sort_store) {
					// 以二进制方式对比两个模式
                    cmp = compareStringObjects(so1->u.cmpobj,so2->u.cmpobj);
                } else {
                    /* Here we can use strcoll() directly as we are sure that
                     * the objects are decoded string objects. */
					// 以本地编码对比两个模式
                    cmp = strcoll(so1->u.cmpobj->ptr,so2->u.cmpobj->ptr);
                }
            }

        } else {


            /* Compare elements directly. */
			// 对比字符串本身

            if (server.sort_store) {
				// 以二进制方式对比字符串对象
                cmp = compareStringObjects(so1->obj,so2->obj);
            } else {
				// 以本地编码对比字符串对象
                cmp = collateStringObjects(so1->obj,so2->obj);
            }
        }
    }

    return server.sort_desc ? -cmp : cmp;
}

sort命令函数如下:

/* The SORT command is the most complex command in Redis. Warning: this code
 * is optimized for speed and a bit less for readability */
void sortCommand(redisClient *c) {
    list *operations;
    unsigned int outputlen = 0;
    int desc = 0, alpha = 0;
    long limit_start = 0, limit_count = -1, start, end;
    int j, dontsort = 0, vectorlen;
    int getop = 0; /* GET operation counter */
    int int_convertion_error = 0;
    int syntax_error = 0;
    robj *sortval, *sortby = NULL, *storekey = NULL;
    redisSortObject *vector; /* Resulting vector to sort */

    /* Lookup the key to sort. It must be of the right types */
	// 获取要排序的键,并检查他是否可以被排序的类型
    sortval = lookupKeyRead(c->db,c->argv[1]);
    if (sortval && sortval->type != REDIS_SET &&
                   sortval->type != REDIS_LIST &&
                   sortval->type != REDIS_ZSET)
    {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    /* Create a list of operations to perform for every sorted element.
     * Operations can be GET/DEL/INCR/DECR */
	// 创建一个链表,链表中保存了要对所有已排序元素执行的操作
	// 操作可以是 GET 、 DEL 、 INCR 或者 DECR
    operations = listCreate();
    listSetFreeMethod(operations,zfree);

	// 指向参数位置
    j = 2; /* options start at argv[2] */

    /* Now we need to protect sortval incrementing its count, in the future
     * SORT may have options able to overwrite/delete keys during the sorting
     * and the sorted key itself may get destroyed */
	// 为 sortval 的引用计数增一
	// 在将来, SORT 命令可以在排序某个键的过程中,覆盖或者删除那个键
    if (sortval)
        incrRefCount(sortval);
    else
        sortval = createListObject();

    /* The SORT command has an SQL-alike syntax, parse it */
	// 读入并分析 SORT 命令的选项
    while(j < c->argc) {

        int leftargs = c->argc-j-1;

		// ASC 选项
        if (!strcasecmp(c->argv[j]->ptr,"asc")) {
            desc = 0;

		// DESC 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"desc")) {
            desc = 1;

		// ALPHA 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"alpha")) {
            alpha = 1;

		// LIMIT 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"limit") && leftargs >= 2) {
			// start 参数和 count 参数
            if ((getLongFromObjectOrReply(c, c->argv[j+1], &limit_start, NULL)
                 != REDIS_OK) ||
                (getLongFromObjectOrReply(c, c->argv[j+2], &limit_count, NULL)
                 != REDIS_OK))
            {
                syntax_error++;
                break;
            }
            j+=2;

		// STORE 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"store") && leftargs >= 1) {
			// 目标键
            storekey = c->argv[j+1];
            j++;

		// BY 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"by") && leftargs >= 1) {

			// 排序的顺序由这个模式决定
            sortby = c->argv[j+1];

            /* If the BY pattern does not contain '*', i.e. it is constant,
             * we don't need to sort nor to lookup the weight keys. */
			// 如果 sortby 模式里面不包含 '*' 符号,
            // 那么无须执行排序操作
            if (strchr(c->argv[j+1]->ptr,'*') == NULL) {
                dontsort = 1;
            } else {
                /* If BY is specified with a real patter, we can't accept
                 * it in cluster mode. */
                if (server.cluster_enabled) {
                    addReplyError(c,"BY option of SORT denied in Cluster mode.");
                    syntax_error++;
                    break;
                }
            }
            j++;

		// GET 选项
        } else if (!strcasecmp(c->argv[j]->ptr,"get") && leftargs >= 1) {

			// 创建一个 GET 操作

            // 不能在集群模式下使用 GET 选项
            if (server.cluster_enabled) {
                addReplyError(c,"GET option of SORT denied in Cluster mode.");
                syntax_error++;
                break;
            }
            listAddNodeTail(operations,createSortOperation(
                REDIS_SORT_GET,c->argv[j+1]));
            getop++;
            j++;

		// 未知选项,语法出错
        } else {
            addReply(c,shared.syntaxerr);
            syntax_error++;
            break;
        }

        j++;
    }

    /* Handle syntax errors set during options parsing. */
    if (syntax_error) {
        decrRefCount(sortval);
        listRelease(operations);
        return;
    }

    /* For the STORE option, or when SORT is called from a Lua script,
     * we want to force a specific ordering even when no explicit ordering
     * was asked (SORT BY nosort). This guarantees that replication / AOF
     * is deterministic.
	 *
	 * 对于 STORE 选项,以及从 Lua 脚本中调用 SORT 命令的情况来看,
	 * 我们想即使在没有指定排序方式的情况下,也强制指定一个排序方法。
	 * 这可以保证复制/AOF 是确定性的。
     *
     * However in the case 'dontsort' is true, but the type to sort is a
     * sorted set, we don't need to do anything as ordering is guaranteed
     * in this special case. 
	 *
	 * 在 dontsort 为真,并且被排序的键不是有序集合时,
	 * 我们才需要为排序指定排序方式,
     * 因为有序集合的成员已经是有序的了。
	 */
    if ((storekey || c->flags & REDIS_LUA_CLIENT) &&
        (dontsort && sortval->type != REDIS_ZSET))
    {
        /* Force ALPHA sorting */
		// 强制 ALPHA 排序
        dontsort = 0;
        alpha = 1;
        sortby = NULL;
    }

    /* Destructively convert encoded sorted sets for SORT. */
	// 被排序的有序集合必须是 SKIPLIST 编码的
    // 如果不是的话,那么将它转换成 SKIPLIST 编码
    if (sortval->type == REDIS_ZSET)
        zsetConvert(sortval, REDIS_ENCODING_SKIPLIST);

    /* Objtain the length of the object to sort. */
	// 获取要排序对象的长度
    switch(sortval->type) {
    case REDIS_LIST: vectorlen = listTypeLength(sortval); break;
    case REDIS_SET: vectorlen =  setTypeSize(sortval); break;
    case REDIS_ZSET: vectorlen = dictSize(((zset*)sortval->ptr)->dict); break;
    default: vectorlen = 0; redisPanic("Bad SORT type"); /* Avoid GCC warning */
    }

    /* Perform LIMIT start,count sanity checking. */
	// 对 LIMIT 选项的 start 和 count 参数进行检查
    start = (limit_start < 0) ? 0 : limit_start;
    end = (limit_count < 0) ? vectorlen-1 : start+limit_count-1;
    if (start >= vectorlen) {
        start = vectorlen-1;
        end = vectorlen-2;
    }
    if (end >= vectorlen) end = vectorlen-1;

    /* Optimization:
	 * 优化
     *
     * 1) if the object to sort is a sorted set.
	 *    如果排序的对象是有序集合
     * 2) There is nothing to sort as dontsort is true (BY <constant string>).
	 *	  dontsort 为真,表示没有什么需要排序
     * 3) We have a LIMIT option that actually reduces the number of elements
     *    to fetch.
	 *	  LIMIT 选项所设置的范围比起有序集合的长度要小
     *
     * In this case to load all the objects in the vector is a huge waste of
     * resources. We just allocate a vector that is big enough for the selected
     * range length, and make sure to load just this part in the vector. 
	 *
	 * 在这种情况下,不需要载入有序集合中的所有元素,只要载入给定范围(range)内的元素就可以了。
	 */
    if (sortval->type == REDIS_ZSET &&
        dontsort &&
        (start != 0 || end != vectorlen-1))
    {
        vectorlen = end-start+1;
    }

    /* Load the sorting vector with all the objects to sort */
	// 创建 redisSortObject 数组
    vector = zmalloc(sizeof(redisSortObject)*vectorlen);
    j = 0;

	// 将列表项放入数组
    if (sortval->type == REDIS_LIST) {
        listTypeIterator *li = listTypeInitIterator(sortval,0,REDIS_TAIL);
        listTypeEntry entry;
        while(listTypeNext(li,&entry)) {
            vector[j].obj = listTypeGet(&entry);
            vector[j].u.score = 0;
            vector[j].u.cmpobj = NULL;
            j++;
        }
        listTypeReleaseIterator(li);

	// 将集合元素放入数组
    } else if (sortval->type == REDIS_SET) {
        setTypeIterator *si = setTypeInitIterator(sortval);
        robj *ele;
        while((ele = setTypeNextObject(si)) != NULL) {
            vector[j].obj = ele;
            vector[j].u.score = 0;
            vector[j].u.cmpobj = NULL;
            j++;
        }
        setTypeReleaseIterator(si);

	// 在 dontsort 为真的情况下
	// 将有序集合的部分成员放进数组
    } else if (sortval->type == REDIS_ZSET && dontsort) {
        /* Special handling for a sorted set, if 'dontsort' is true.
         * This makes sure we return elements in the sorted set original
         * ordering, accordingly to DESC / ASC options.
         *
         * Note that in this case we also handle LIMIT here in a direct
         * way, just getting the required range, as an optimization. */

		// 这是前面说过的,可以进行优化的 case

        zset *zs = sortval->ptr;
        zskiplist *zsl = zs->zsl;
        zskiplistNode *ln;
        robj *ele;
        int rangelen = vectorlen;

        /* Check if starting point is trivial, before doing log(N) lookup. */
		// 根据 desc 或者 asc 排序,指向初始节点
        if (desc) {
            long zsetlen = dictSize(((zset*)sortval->ptr)->dict);

            ln = zsl->tail;
            if (start > 0)
                ln = zslGetElementByRank(zsl,zsetlen-start);
        } else {
            ln = zsl->header->level[0].forward;
            if (start > 0)
                ln = zslGetElementByRank(zsl,start+1);
        }

		// 遍历范围中的所有节点,并放进数组
        while(rangelen--) {
            redisAssertWithInfo(c,sortval,ln != NULL);
            ele = ln->obj;
            vector[j].obj = ele;
            vector[j].u.score = 0;
            vector[j].u.cmpobj = NULL;
            j++;
            ln = desc ? ln->backward : ln->level[0].forward;
        }
        /* The code producing the output does not know that in the case of
         * sorted set, 'dontsort', and LIMIT, we are able to get just the
         * range, already sorted, so we need to adjust "start" and "end"
         * to make sure start is set to 0. */
        end -= start;
        start = 0;

	// 普通情况下的有序集合,将所有集合成员放进数组
    } else if (sortval->type == REDIS_ZSET) {
        dict *set = ((zset*)sortval->ptr)->dict;
        dictIterator *di;
        dictEntry *setele;
        di = dictGetIterator(set);
        while((setele = dictNext(di)) != NULL) {
            vector[j].obj = dictGetKey(setele);
            vector[j].u.score = 0;
            vector[j].u.cmpobj = NULL;
            j++;
        }
        dictReleaseIterator(di);
    } else {
        redisPanic("Unknown type");
    }
    redisAssertWithInfo(c,sortval,j == vectorlen);

    /* Now it's time to load the right scores in the sorting vector */
	// 载入权重值
    if (dontsort == 0) {

        for (j = 0; j < vectorlen; j++) {
            robj *byval;

			// 如果使用了 BY 选项,那么就根据指定的对象作为权重
            if (sortby) {
                /* lookup value to sort by */
                byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
                if (!byval) continue;
			// 如果没有使用 BY 选项,那么使用对象本身作为权重
            } else {
                /* use object itself to sort by */
                byval = vector[j].obj;
            }

			// 如果是 ALPHA 排序,那么将对比对象改为解码后的 byval
            if (alpha) {
                if (sortby) vector[j].u.cmpobj = getDecodedObject(byval);
			// 否则,将字符串对象转换成 double 类型
            } else {
                if (sdsEncodedObject(byval)) {
                    char *eptr;
					// 将字符串转换成 double 类型
                    vector[j].u.score = strtod(byval->ptr,&eptr);
                    if (eptr[0] != '\0' || errno == ERANGE ||
                        isnan(vector[j].u.score))
                    {
                        int_convertion_error = 1;
                    }
                } else if (byval->encoding == REDIS_ENCODING_INT) {
                    /* Don't need to decode the object if it's
                     * integer-encoded (the only encoding supported) so
                     * far. We can just cast it */
					// 直接将整数设置为权重
                    vector[j].u.score = (long)byval->ptr;
                } else {
                    redisAssertWithInfo(c,sortval,1 != 1);
                }
            }

            /* when the object was retrieved using lookupKeyByPattern,
             * its refcount needs to be decreased. */
            if (sortby) {
                decrRefCount(byval);
            }
        }

    }

	// 排序
    if (dontsort == 0) {
        server.sort_desc = desc;
        server.sort_alpha = alpha;
        server.sort_bypattern = sortby ? 1 : 0;
        server.sort_store = storekey ? 1 : 0;

        if (sortby && (start != 0 || end != vectorlen-1))
            pqsort(vector,vectorlen,sizeof(redisSortObject),sortCompare, start,end);
        else
            qsort(vector,vectorlen,sizeof(redisSortObject),sortCompare);
    }

    /* Send command output to the output buffer, performing the specified
     * GET/DEL/INCR/DECR operations if any. */
	// 将命令的输出放到输出缓冲区
	// 然后执行给定的 GET / DEL / INCR 或 DECR 操作
    outputlen = getop ? getop*(end-start+1) : end-start+1;
    if (int_convertion_error) {
        addReplyError(c,"One or more scores can't be converted into double");
    } else if (storekey == NULL) {

        /* STORE option not specified, sent the sorting result to client */
		// STORE 选项未使用,直接将排序结果发送给客户端

        addReplyMultiBulkLen(c,outputlen);
        for (j = start; j <= end; j++) {
            listNode *ln;
            listIter li;

			// 没有设置 GET 选项,直接将结果添加到回复
            if (!getop) addReplyBulk(c,vector[j].obj);

            // 有设置 GET 选项。。。

			// 遍历设置的操作
            listRewind(operations,&li);
            while((ln = listNext(&li))) {
                redisSortOperation *sop = ln->value;

				// 解释并查找键
                robj *val = lookupKeyByPattern(c->db,sop->pattern,
                    vector[j].obj);

				// 执行 GET 操作,将指定键的值添加到回复
                if (sop->type == REDIS_SORT_GET) {
                    if (!val) {
                        addReply(c,shared.nullbulk);
                    } else {
                        addReplyBulk(c,val);
                        decrRefCount(val);
                    }

				// DEL 、INCR 和 DECR 操作都尚未实现
                } else {
                    /* Always fails */
                    redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
                }
            }
        }
    } else {
        robj *sobj = createZiplistObject();

        /* STORE option specified, set the sorting result as a List object */
		// 已设置 STORE 选项,将排序结果保存到列表对象

        for (j = start; j <= end; j++) {
            listNode *ln;
            listIter li;

			// 没有 GET ,直接返回排序元素
            if (!getop) {
                listTypePush(sobj,vector[j].obj,REDIS_TAIL);

			// 有 GET ,获取指定的键
            } else {
                listRewind(operations,&li);
                while((ln = listNext(&li))) {
                    redisSortOperation *sop = ln->value;
                    robj *val = lookupKeyByPattern(c->db,sop->pattern,
                        vector[j].obj);

                    if (sop->type == REDIS_SORT_GET) {
                        if (!val) val = createStringObject("",0);

                        /* listTypePush does an incrRefCount, so we should take care
                         * care of the incremented refcount caused by either
                         * lookupKeyByPattern or createStringObject("",0) */
                        listTypePush(sobj,val,REDIS_TAIL);
                        decrRefCount(val);
                    } else {
                        /* Always fails */
                        redisAssertWithInfo(c,sortval,sop->type == REDIS_SORT_GET);
                    }
                }
            }
        }

		// 如果排序结果不为空,那么将结果列表关联到数据库键,并发送事件
        if (outputlen) {
            setKey(c->db,storekey,sobj);
            notifyKeyspaceEvent(REDIS_NOTIFY_LIST,"sortstore",storekey,
                                c->db->id);
            server.dirty += outputlen;
		// 如果排序结果为空,那么只要删除 storekey 就可以了,因为没有结果可以保存
        } else if (dbDelete(c->db,storekey)) {
            signalModifiedKey(c->db,storekey);
            notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",storekey,c->db->id);
            server.dirty++;
        }
        decrRefCount(sobj);
        addReplyLongLong(c,outputlen);
    }

    /* Cleanup */
    if (sortval->type == REDIS_LIST || sortval->type == REDIS_SET)
        for (j = 0; j < vectorlen; j++)
            decrRefCount(vector[j].obj);
    decrRefCount(sortval);
    listRelease(operations);
    for (j = 0; j < vectorlen; j++) {
        if (alpha && vector[j].u.cmpobj)
            decrRefCount(vector[j].u.cmpobj);
    }
    zfree(vector);
}


总结

本文对Redis sort命令进行了简要介绍,如有不当,请多多指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值