redis源码分析与思考(十七)——有序集合类型的命令实现(t_zset.c)

版权声明:博主GitHub地址https://github.com/suyeq欢迎大家前来交流学习 https://blog.csdn.net/hackersuye/article/details/83117866

    有序集合是集合的延伸,它保存着集合元素的不可重复性,但不同的是,它是有序的,它利用每一个元素的分数来作为有序集合的排序依据,现在列出有序集合的命令:

有序集合命令

命令 对应操作 时间复杂度
zadd key score member [score member…] 添加成员 O(n)
zcard key 计算成员个数 O(1)
zscore key member 计算成员的分数 O(1)
zrank key member 计算成员的排名 O(logn)
zrem key member [member…] 删除成员 O(logn)
zincrby key increment member 增加成员的分数 O(logn)
zrange key start end [withscores] 返回指定排名的成员 O(logn)
zrangebyscore key min max [withscores] 返回指定分数范围内的成员 O(logn)
zcount key min max 返回指定分数范围内的成员个数 O(logn)
zremrangebyrank key start end 删除第start到第end名的成员 O(logn)
zremrangebyscore key min max 删除指定分数范围内的成员 O(logn)
zinterstore destination numkeys key [key…] 计算交集保存在目标键 O(logn)
zunionstore destination numkeys key [key…] 就算并集保存在目标键 O(logn)

    与集合类型一样,有序集合也只讲交集与并集以及编码的转换,有序集合的其它操作可参照之前写的压缩列表与跳跃表的实现。

编码的转换

    当有序集合里面的元素个数小于默认的128个,且每个元素的值都小于64字节的时候,redis会采用压缩列表来实现存贮,不满足上述任意情况都将实行编码转换,因为那时压缩列表的效率会变低。但是与集合不同的是,编码转换会回转,即有序集合满足上述情况下,跳跃表编码的有序集合会回到压缩列表编码的有序集合:

/*
 * 将跳跃表对象 zobj 的底层编码转换为 encoding 。
 */
void zsetConvert(robj *zobj, int encoding) {
    zset *zs;
    zskiplistNode *node, *next;
    robj *ele;
    double score;
    if (zobj->encoding == encoding) return;
    // 从 ZIPLIST 编码转换为 SKIPLIST 编码
    if (zobj->encoding == REDIS_ENCODING_ZIPLIST) {
        unsigned char *zl = zobj->ptr;
        unsigned char *eptr, *sptr;
        unsigned char *vstr;
        unsigned int vlen;
        long long vlong;
        if (encoding != REDIS_ENCODING_SKIPLIST)
            redisPanic("Unknown target encoding");
        // 创建有序集合结构
        zs = zmalloc(sizeof(*zs));
        // 字典
        zs->dict = dictCreate(&zsetDictType,NULL);
        // 跳跃表
        zs->zsl = zslCreate();
        // 有序集合在 ziplist 中的排列:
        // 指向 ziplist 中的首个节点(保存着元素成员)
        eptr = ziplistIndex(zl,0);
        redisAssertWithInfo(NULL,zobj,eptr != NULL);
        // 指向 ziplist 中的第二个节点(保存着元素分值)
        sptr = ziplistNext(zl,eptr);
        redisAssertWithInfo(NULL,zobj,sptr != NULL);
        // 遍历所有 ziplist 节点,并将元素的成员和分值添加到有序集合中
        while (eptr != NULL) {    
            // 取出分值
            score = zzlGetScore(sptr);
            // 取出成员
redisAssertWithInfo(NULL,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
            if (vstr == NULL)
                ele = createStringObjectFromLongLong(vlong);
            else
                ele = createStringObject((char*)vstr,vlen);
            // 将成员和分值分别关联到跳跃表和字典中
            node = zslInsert(zs->zsl,score,ele);
            redisAssertWithInfo(NULL,zobj,dictAdd(zs->dict,ele,&node->score) == DICT_OK);
            incrRefCount(ele); /* Added to dictionary. */
            // 移动指针,指向下个元素
            zzlNext(zl,&eptr,&sptr);
        }
        // 释放原来的 ziplist
        zfree(zobj->ptr);
        // 更新对象的值,以及编码方式
        zobj->ptr = zs;
        zobj->encoding = REDIS_ENCODING_SKIPLIST;
    // 从 SKIPLIST 转换为 ZIPLIST 编码
    } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
        // 新的 ziplist
        unsigned char *zl = ziplistNew();
        if (encoding != REDIS_ENCODING_ZIPLIST)
            redisPanic("Unknown target encoding");
        // 指向跳跃表
        zs = zobj->ptr;
        // 先释放字典,因为只需要跳跃表就可以遍历整个有序集合了
        dictRelease(zs->dict);
        // 指向跳跃表首个节点
        node = zs->zsl->header->level[0].forward;
        // 释放跳跃表表头
        zfree(zs->zsl->header);
        zfree(zs->zsl);
        // 遍历跳跃表,取出里面的元素,并将它们添加到 ziplist
        while (node) {
            // 取出解码后的值对象
            ele = getDecodedObject(node->obj);
            // 添加元素到 ziplist
            zl = zzlInsertAt(zl,NULL,ele,node->score);
            decrRefCount(ele);
            // 沿着跳跃表的第 0 层前进
            next = node->level[0].forward;
            zslFreeNode(node);
            node = next;
        }
        // 释放跳跃表
        zfree(zs);
        // 更新对象的值,以及对象的编码方式
        zobj->ptr = zl;
        zobj->encoding = REDIS_ENCODING_ZIPLIST;
    } else {
        redisPanic("Unknown sorted set encoding");
    }
}

交集、并集

    有序集合的交集与并集的底层实现也在一个函数里面,与集合的交并集计算原理差不多,多了权值的计算,并集的时候没有算法的选择:

/**
 *
 * @param c 客户端
 * @param dstkey  保存的目标有序集合键
 * @param op 判断交集还是并集
 */
void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
    int i, j;
    long setnum;
    int aggregate = REDIS_AGGR_SUM;
    zsetopsrc *src;
    zsetopval zval;
    robj *tmp;
    unsigned int maxelelen = 0;
    robj *dstobj;
    zset *dstzset;
    zskiplistNode *znode;
    int touched = 0;
    /* expect setnum input keys to be given */
    // 取出要处理的有序集合的个数 setnum
    if ((getLongFromObjectOrReply(c, c->argv[2], &setnum, NULL) != REDIS_OK))
        return;
    if (setnum < 1) {
        //向客户端返回错误信息
        addReplyError(c,
            "at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE");
        return;
    }
    /* test if the expected number of keys would overflow */
    // setnum 参数和传入的 key 数量不相同,出错
    if (setnum > c->argc-3) {
        addReply(c,shared.syntaxerr);
        return;
    }
    /* read keys to be used for input */
    // 为每个输入 key 创建一个迭代器
    src = zcalloc(sizeof(zsetopsrc) * setnum);
    for (i = 0, j = 3; i < setnum; i++, j++) {
        // 取出 key 对象
        robj *obj = lookupKeyWrite(c->db,c->argv[j]);
        // 创建迭代器
        if (obj != NULL) {
            if (obj->type != REDIS_ZSET && obj->type != REDIS_SET) {
                zfree(src);
                addReply(c,shared.wrongtypeerr);
                return;
            }
            src[i].subject = obj;
            src[i].type = obj->type;
            src[i].encoding = obj->encoding;
        // 不存在的对象设为 NULL
        } else {
            src[i].subject = NULL;
        }
        /* Default all weights to 1. */
        // 默认权重为 1.0
        src[i].weight = 1.0;
    }
    /* parse optional extra arguments */
    // 分析并读入可选参数
    if (j < c->argc) {
        int remaining = c->argc - j;
        while (remaining) {
            if (remaining >= (setnum + 1) && !strcasecmp(c->argv[j]->ptr,"weights")) {
                j++; remaining--;
                // 权重参数
                for (i = 0; i < setnum; i++, j++, remaining--) {
                    if (getDoubleFromObjectOrReply(c,c->argv[j],&src[i].weight,
                            "weight value is not a float") != REDIS_OK)
                    {
                        zfree(src);
                        return;
                    }
                }
            } else if (remaining >= 2 && !strcasecmp(c->argv[j]->ptr,"aggregate")) {
                j++; remaining--;
                // 判断聚合方式
                if (!strcasecmp(c->argv[j]->ptr,"sum")) {
                    aggregate = REDIS_AGGR_SUM;
                } else if (!strcasecmp(c->argv[j]->ptr,"min")) {
                    aggregate = REDIS_AGGR_MIN;
                } else if (!strcasecmp(c->argv[j]->ptr,"max")) {
                    aggregate = REDIS_AGGR_MAX;
                } else {
                    zfree(src);
                    addReply(c,shared.syntaxerr);
                    return;
                }
                j++; remaining--;
            } else {
                zfree(src);
                addReply(c,shared.syntaxerr);
                return;
            }
        }
    }
    /* sort sets from the smallest to largest, this will improve our
     * algorithm's performance */
    // 对所有集合进行排序,以减少算法的常数项
    qsort(src,setnum,sizeof(zsetopsrc),zuiCompareByCardinality);
    // 创建结果集对象
    dstobj = createZsetObject();
    dstzset = dstobj->ptr;
    memset(&zval, 0, sizeof(zval));
    // ZINTERSTORE 命令
    if (op == REDIS_OP_INTER) {
        /* Skip everything if the smallest input is empty. */
        // 只处理非空集合
        if (zuiLength(&src[0]) > 0) {
            /* Precondition: as src[0] is non-empty and the inputs are ordered
             * by size, all src[i > 0] are non-empty too. */
            // 遍历基数最小的 src[0] 集合
            zuiInitIterator(&src[0]);
            while (zuiNext(&src[0],&zval)) {
                double score, value;
                // 计算加权分值
                score = src[0].weight * zval.score;
                if (isnan(score)) score = 0;
                // 将 src[0] 集合中的元素和其他集合中的元素做加权聚合计算
                for (j = 1; j < setnum; j++) {
                    /* It is not safe to access the zset we are
                     * iterating, so explicitly check for equal object. */
                    // 如果当前迭代到的 src[j] 的对象和 src[0] 的对象一样,
                    // 那么 src[0] 出现的元素必然也出现在 src[j]
                    // 那么我们可以直接计算聚合值,
                    // 不必进行 zuiFind 去确保元素是否出现
                    // 这种情况在某个 key 输入了两次,
                    // 并且这个 key 是所有输入集合中基数最小的集合时会出现
                    if (src[j].subject == src[0].subject) {
                        value = zval.score*src[j].weight;
   zunionInterAggregate(&score,value,aggregate);
                    // 如果能在其他集合找到当前迭代到的元素的话
                    // 那么进行聚合计算
                    } else if (zuiFind(&src[j],&zval,&value)) {
                        value *= src[j].weight;
      zunionInterAggregate(&score,value,aggregate);
                    // 如果当前元素没出现在某个集合,那么跳出 for 循环
                    // 处理下个元素
                    } else {
                        break;
                    }
                }
                /* Only continue when present in every input. */
                // 只在交集元素出现时,才执行以下代码
                if (j == setnum) {
                    // 取出值对象
                    tmp = zuiObjectFromValue(&zval);
                    // 加入到有序集合中
                    znode = zslInsert(dstzset->zsl,score,tmp);
                    incrRefCount(tmp); /* added to skiplist */
                    // 加入到字典中
                    dictAdd(dstzset->dict,tmp,&znode->score);
                    incrRefCount(tmp); /* added to dictionary */
                    // 更新字符串对象的最大长度
                    if (sdsEncodedObject(tmp)) {
                        if (sdslen(tmp->ptr) > maxelelen)
                            maxelelen = sdslen(tmp->ptr);
                    }
                }
            }
            zuiClearIterator(&src[0]);
        }
    // ZUNIONSTORE
    } else if (op == REDIS_OP_UNION) {
        // 遍历所有输入集合
        for (i = 0; i < setnum; i++) {
            // 跳过空集合
            if (zuiLength(&src[i]) == 0)
                continue;
            // 遍历所有集合元素
            zuiInitIterator(&src[i]);
            while (zuiNext(&src[i],&zval)) {
                double score, value;
                /* Skip an element that when already processed */
                // 跳过已处理元素
                if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
                    continue;
                /* Initialize score */
                // 初始化分值
                score = src[i].weight * zval.score;
                // 溢出时设为 0
                if (isnan(score)) score = 0;
                /* We need to check only next sets to see if this element
                 * exists, since we process every element just one time so
                 * it can't exist in a previous set (otherwise it would be
                 * already processed). */
                for (j = (i+1); j < setnum; j++) {
                    /* It is not safe to access the zset we are
                     * iterating, so explicitly check for equal object. */
                    // 当前元素的集合和被迭代集合一样
                    // 所以同一个元素必然出现在 src[j] 和 src[i]
                    // 程序直接计算它们的聚合值
                    // 而不必使用 zuiFind 来检查元素是否存在
                    if(src[j].subject == src[i].subject) {
                        value = zval.score*src[j].weight;
                        zunionInterAggregate(&score,value,aggregate);

                    // 检查成员是否存在
                    } else if (zuiFind(&src[j],&zval,&value)) {
                        value *= src[j].weight;
                        zunionInterAggregate(&score,value,aggregate);
                    }
                }
                // 取出成员
                tmp = zuiObjectFromValue(&zval);
                // 插入并集元素到跳跃表
                znode = zslInsert(dstzset->zsl,score,tmp);
                incrRefCount(zval.ele); /* added to skiplist */
                // 添加元素到字典
                dictAdd(dstzset->dict,tmp,&znode->score);
                incrRefCount(zval.ele); /* added to dictionary */
                // 更新字符串最大长度
                if (sdsEncodedObject(tmp)) {
                    if (sdslen(tmp->ptr) > maxelelen)
                        maxelelen = sdslen(tmp->ptr);
                }
            }
            zuiClearIterator(&src[i]);
        }
    } else {
        redisPanic("Unknown operator");
    }
    // 删除已存在的 dstkey ,等待后面用新对象代替它
    if (dbDelete(c->db,dstkey)) {
        signalModifiedKey(c->db,dstkey);
        touched = 1;
        server.dirty++;
    }
    // 如果结果集合的长度不为 0 
    if (dstzset->zsl->length) {
        /* Convert to ziplist when in limits. */
        // 看是否需要对结果集合进行编码转换
        if (dstzset->zsl->length <= server.zset_max_ziplist_entries &&
            maxelelen <= server.zset_max_ziplist_value)
                zsetConvert(dstobj,REDIS_ENCODING_ZIPLIST);
        // 将结果集合关联到数据库
        dbAdd(c->db,dstkey,dstobj);
        // 回复结果集合的长度
        addReplyLongLong(c,zsetLength(dstobj));
        if (!touched) signalModifiedKey(c->db,dstkey);
        notifyKeyspaceEvent(REDIS_NOTIFY_ZSET,
            (op == REDIS_OP_UNION) ? "zunionstore" : "zinterstore",
            dstkey,c->db->id);
        server.dirty++;
    // 结果集为空
    } else {
        decrRefCount(dstobj);
        addReply(c,shared.czero);
        if (touched)
           notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",dstkey,c->db->id);
    }
    zfree(src);
}

猜你喜欢

转载自blog.csdn.net/hackersuye/article/details/83117866