Redis源码学习简记(十 一)t_set与t_zset原理与个人理解

        这次把两个set放在一起来说吧。经过前面的hash与list的分析,这两个的实现感觉其实比前面的要简单一些的。

        首先来说一下这个t_set吧。这个set的实现主要是由intset与dict两种编码模式实现的。其中intset只能存储整型,而且是有序存储的。而dict就是前面熟知的字典实现的hash。来看看一些set的基本实现吧。

从其创建可以看到,如上所说的也就两种编码模式。

/* Factory method to return a set that *can* hold "value". When the object has
 * an integer-encodable value, an intset will be returned. Otherwise a regular
 * hash table. */
robj *setTypeCreate(sds value) {
    //setType总共两种类型 intset或者 dict
    //intset是有序的
    if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
        return createIntsetObject();
    return createSetObject();
}

根据类型来添加元素,而使用dict的话会浪费key-value中的一个键位。只使用其中一个空间进行存储。

/* Add the specified value into a set.
 *
 * If the value was already member of the set, nothing is done and 0 is
 * returned, otherwise the new element is added and 1 is returned. */
int setTypeAdd(robj *subject, sds value) {
    //根据类型添加元素
    long long llval;
    if (subject->encoding == OBJ_ENCODING_HT) {
        dict *ht = subject->ptr;
        //若为hash编码则在集合中新加value 新键
        dictEntry *de = dictAddRaw(ht,value,NULL);
        if (de) {
            //设置key与val的值
            dictSetKey(ht,de,sdsdup(value));
            dictSetVal(ht,de,NULL);
            return 1;
        }
    } else if (subject->encoding == OBJ_ENCODING_INTSET) {
        //若编码为intset 那么将value转化为整型
        if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
            uint8_t success = 0;
            subject->ptr = intsetAdd(subject->ptr,llval,&success);
            //将llval加入到ptr所指的intset数据结构中
            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 {
            //若不能转化为整型,那么将subject转成dict
            //然后添加value - null 键值对
            /* 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;
}

删除元素

int setTypeIsMember(robj *subject, sds value) {
    //查找value在subject是否存在存在返回1 否则返回0
    long long llval;
    if (subject->encoding == OBJ_ENCODING_HT) {
        return dictFind((dict*)subject->ptr,value) != NULL;
    } else if (subject->encoding == OBJ_ENCODING_INTSET) {
        if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
            return intsetFind((intset*)subject->ptr,llval);
        }
    } else {
        serverPanic("Unknown set encoding");
    }
    return 0;
}

set的迭代器创建

setTypeIterator *setTypeInitIterator(robj *subject) {
    /*
    typedef struct {
        robj *subject;
        int encoding;
        int ii; //intset iterator 
        dictIterator *di;
    } setTypeIterator;

    set迭代器结构体。

    */
    //下面根据类型初始化结构体
    setTypeIterator *si = zmalloc(sizeof(setTypeIterator));
    si->subject = subject;
    si->encoding = subject->encoding;
    if (si->encoding == OBJ_ENCODING_HT) {
        si->di = dictGetIterator(subject->ptr);
    } else if (si->encoding == OBJ_ENCODING_INTSET) {
        si->ii = 0;
    } else {
        serverPanic("Unknown set encoding");
    }
    return si;
}

迭代器的next函数

int setTypeNext(setTypeIterator *si, sds *sdsele, int64_t *llele) {
    //不成功则返回-1 否则返回迭代器的编码类型
    if (si->encoding == OBJ_ENCODING_HT) {
        //若编码为dict则调用dict的api获得值
        //而在set使用dict存储 所有的值存在key value为null
        dictEntry *de = dictNext(si->di);
        if (de == NULL) return -1;
        *sdsele = dictGetKey(de);
        *llele = -123456789; /* Not needed. Defensive. */
        //将不使用的llele初始化
    } else if (si->encoding == OBJ_ENCODING_INTSET) {
        //若为intset则通过si->ii判定当前迭代器指向第几个元素
        if (!intsetGet(si->subject->ptr,si->ii++,llele))
            return -1;
        *sdsele = NULL; /* Not needed. Defensive. */
    } else {
        serverPanic("Wrong set encoding in setTypeNext");
    }
    return si->encoding;
}

以sds返回set中的元素

sds setTypeNextObject(setTypeIterator *si) {
    int64_t intele;
    sds sdsele;
    int encoding;
    //调用上面的函数,返回的必定为sds字符串

    encoding = setTypeNext(si,&sdsele,&intele);
    switch(encoding) {
        case -1:    return NULL;
        case OBJ_ENCODING_INTSET:
            return sdsfromlonglong(intele);
        case OBJ_ENCODING_HT:
            return sdsdup(sdsele);
        default:
            serverPanic("Unsupported encoding");
    }
    return NULL; /* just to suppress warnings */
}

set中随机获取元素,返回该元素的编码方式。

int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
    //随机获得一个set中的值,然后保存在参数中,返回集合的编码类型
    if (setobj->encoding == OBJ_ENCODING_HT) {
        dictEntry *de = dictGetRandomKey(setobj->ptr);
        *sdsele = dictGetKey(de);
        *llele = -123456789; /* Not needed. Defensive. */
    } else if (setobj->encoding == OBJ_ENCODING_INTSET) {
        *llele = intsetRandom(setobj->ptr);
        *sdsele = NULL; /* Not needed. Defensive. */
    } else {
        serverPanic("Unknown set encoding");
    }
    return setobj->encoding;
}

最后来看一下编码转换到函数吧。

void setTypeConvert(robj *setobj, int enc) {
    //将set的编码从 intset转换为dict编码类型
    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));
        //插入元素前,首先将容量变成set的长度

        /* 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");
    }
}

剩下的都是command,其逻辑与实现都不难。有兴趣可以自己看看就好了。

set的实现大概明了后,来看看zset的实现方式吧。zset的话是整个类型都是以有序的方式存储的。

zset分为两种编码类型。一种是以ziplist用来存储方式,以ele,score一对一对存储,根据score进行排序。另外一种是使用zset结构体来存储。

    typedef struct zset {
    dict *dict;
    //用于维护 value与score 的映射
    zskiplist *zsl;
    //按score 对元素进行排序,使得为O(logN) 
    } zset;

    zset结构体定义

zset这个结构体,分为dict与跳表两个元素。dict存储value与score组成的键值对,而zskiplist则是以score进行排序的链表。

其实现的api大多数都是逻辑简单,调用前面的基础api进行实现的,存在大量重复的东西,就不细细分析了。

你可能感兴趣的:(Reids源码学习)