redis源码阅读之集合对象

redis当中集合对象的底层实现为intset和hashtable实现,用hashtable实现时,存储具体值的是key,value统一用NULL。其实集合对象的实现和hash对象的实现还是非常类似的,都是尽可能用占用空间小的底层类型存储,如果实在存不下了,就得鸟枪换炮了
老规矩,还是先说转换的条件,由于占地较小的实现为intset,这就导致发生转化的条件比
zipmap->hashtable要不一样了,但也是一共两项,若有一项或一项以上没法满足,则intset转为hashtable:

  1. 集合对象均为整数值;
  2. intset中的元素个数超过512个

其中第二个限制可以在redis.conf文件中修改

# set in order to use this special memory saving encoding.
set-max-intset-entries 512

其转换代码如下:

void setTypeConvert(robj *setobj, int enc) {
    setTypeIterator *si;
    serverAssertWithInfo(NULL,setobj,setobj->type == OBJ_SET &&
                                 setobj->encoding == OBJ_ENCODING_INTSET);
    if (enc == OBJ_ENCODING_HT) {
        int64_t intele;
        dict *d = dictCreate(&setDictType,NULL);
        sds element;
        /* Presize the dict to avoid rehashing */
        dictExpand(d,intsetLen(setobj->ptr));
        /* To add the elements we extract integers and create redis objects */
        si = setTypeInitIterator(setobj);
        while (setTypeNext(si,&element,&intele) != -1) {
            element = sdsfromlonglong(intele);
            serverAssert(dictAdd(d,element,NULL) == DICT_OK);
        }
        setTypeReleaseIterator(si);
        setobj->encoding = OBJ_ENCODING_HT;
        zfree(setobj->ptr);
        setobj->ptr = d;
    } else {
        serverPanic("Unsupported set conversion");
    }
}

其中用到转换代码的函数如下所示:

int setTypeAdd(robj *subject, sds value) {
    long long llval;
    if (subject->encoding == OBJ_ENCODING_HT) {
        /*如果是hashtable,直接加*/
        dict *ht = subject->ptr;
        dictEntry *de = dictAddRaw(ht,value,NULL);
        if (de) {
            dictSetKey(ht,de,sdsdup(value));
            dictSetVal(ht,de,NULL);
            return 1;
        }
    } else if (subject->encoding == OBJ_ENCODING_INTSET) {
        if (isSdsRepresentableAsLonglong(value,&llval) == C_OK) {
            //先判断是否为整数*/
            uint8_t success = 0;
            subject->ptr = intsetAdd(subject->ptr,llval,&success);
            /*插入是否成功*/
            if (success) {
                /* Convert to regular set when the intset contains
                 * too many entries. */
                if (intsetLen(subject->ptr) > server.set_max_intset_entries)
                /*超过阈值*/
                setTypeConvert(subject,OBJ_ENCODING_HT);
                return 1;
            }
        } else {
            /*转换类型*/
            /* Failed to get integer from object, convert to regular set. */
            setTypeConvert(subject,OBJ_ENCODING_HT);
            /* The set *was* an intset and this value is not integer
             * encodable, so dictAdd should always work. */
            serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);
            return 1;
        }
    } else {
        serverPanic("Unknown set encoding");
    }
    return 0;
}

至于其留给客户端的命令接口,这里也只列出一个,其余不再赘述:

void saddCommand(client *c) {
    robj *set;
    int j, added = 0;
    set = lookupKeyWrite(c->db,c->argv[1]);
    /*在db中查找指定key*/
    if (set == NULL) {
        /*为空则创建*/
        set = setTypeCreate(c->argv[2]->ptr);
        dbAdd(c->db,c->argv[1],set);
    } else {
        /*不为空则插入*/
        if (set->type != OBJ_SET) {
            /*判断*/
            addReply(c,shared.wrongtypeerr);
            return;
        }
    }
    for (j = 2; j < c->argc; j++) {
        if (setTypeAdd(set,c->argv[j]->ptr)) added++;
    }
    if (added) {
        signalModifiedKey(c->db,c->argv[1]);
        notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    }
    server.dirty += added;
    addReplyLonglong(c,added);
}

不足之处还请大家多多指正~~~

猜你喜欢

转载自blog.51cto.com/13754022/2381048