Redis源碼剖析和註釋(十二)--- 集合類型鍵實現(t_set)

Redis 集合類型鍵實現(t_set)

1. 集合命令介紹

redis中所有的集合命令如下:Redis集合命令詳解

序號 命令 說明
1 SADD key member1 [member2] 將一個或多個成員添加到集合
2 SCARD key 獲取集合中的成員數
3 SDIFF key1 [key2] 減去多個集合
4 SDIFFSTORE destination key1 [key2] 減去多個集並將結果集存儲在鍵中
5 SINTER key1 [key2] 相交多個集合
6 SINTERSTORE destination key1 [key2] 交叉多個集合並將結果集存儲在鍵中
7 SISMEMBER key member 判斷確定給定值是否是集合的成員
8 SMOVE source destination member 將成員從一個集合移動到另一個集合
9 SPOP key 從集合中刪除並返回隨機成員
10 SRANDMEMBER key [count] 從集合中獲取一個或多個隨機成員
11 SREM key member1 [member2] 從集合中刪除一個或多個成員
12 SUNION key1 [key2] 添加多個集合
13 SUNIONSTORE destination key1 [key2] 添加多個集並將結果集存儲在鍵中
14 SSCAN key cursor [MATCH pattern] [COUNT count] 遞增地迭代集合中的元素

2. 集合類型的實現

之前在redis對象系統源碼剖析和註釋中提到,一個集合類型的對象的編碼有兩種,分別是OBJ_ENCODING_HT和OBJ_ENCODING_INTSET。

編碼—encoding 對象—ptr
OBJ_ENCODING_HT 字典實現的集合對象
OBJ_ENCODING_INTSET 整數集合實現的集合對象

關於集合類型底層的兩種數據結構的源碼剖析和註釋,請看下面的博文。

Redis 字典結構源碼剖析和註釋

Redis 整數集合源碼剖析和註釋

從OBJ_ENCODING_INTSET轉換到OBJ_ENCODING_HT的條件如下:

  • redis的配置文件中的選項:如果數據編碼爲整數集合的集合對象的元素數量超過 set-max-intset-entries 閾值,則會轉換編碼
set-max-intset-entries  512
  • 如果向數據編碼爲整數集合的集合對象插入字符串類型的對象,則會轉換編碼

集合對象的數據編碼轉換的源碼如下:

// 將集合對象的INTSET編碼類型轉換爲enc類型
void setTypeConvert(robj *setobj, int enc) {
    setTypeIterator *si;
    serverAssertWithInfo(NULL,setobj,setobj->type == OBJ_SET &&
                             setobj->encoding == OBJ_ENCODING_INTSET);

    // 轉換成OBJ_ENCODING_HT字典類型的編碼
    if (enc == OBJ_ENCODING_HT) {
        int64_t intele;
        // 創建一個字典
        dict *d = dictCreate(&setDictType,NULL);
        robj *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 = createStringObjectFromLongLong(intele);   //將當前集合中的元素轉換爲字符串類型對象
            serverAssertWithInfo(NULL,element,
                                dictAdd(d,element,NULL) == DICT_OK);
        }
        // 釋放迭代器空間
        setTypeReleaseIterator(si);

        // 設置轉換後的集合對象的編碼類型
        setobj->encoding = OBJ_ENCODING_HT;
        // 更新集合對象的值對象
        zfree(setobj->ptr);
        setobj->ptr = d;
    } else {
        serverPanic("Unsupported set conversion");
    }
}

一個集合對象的結構定義如下:

typedef struct redisObject {
    //對象的數據類型,集合對象應該爲 OBJ_SET
    unsigned type:4;        
    //對象的編碼類型,分別爲 OBJ_ENCODING_INTSET 或 OBJ_ENCODING_HT
    unsigned encoding:4;
    //暫且不關心該成員
    unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
    //引用計數
    int refcount;
    //指向底層數據實現的指針,指向一個dict的字典結構或整數集合結構
    void *ptr;
} robj;

我們假設一個集合保存的是年齡的標籤,有1995、1996、1997、1994

127.0.0.1:6379> SADD tags:age 1997 1995 1994 1996
(integer) 4
127.0.0.1:6379> OBJECT ENCODING tags:age
"intset"
127.0.0.1:6379> SMEMBERS tags:age
1) "1994"
2) "1995"
3) "1996"
4) "1997"

這個 tags:age 集合對象的空間結構可能如下圖:

這裏寫圖片描述

假設一個集合保存的是運動的標籤,有basketball,football,volleyball

127.0.0.1:6379> SADD tags:sport basketball football volleyball
(integer) 3
127.0.0.1:6379> OBJECT ENCODING tags:sport
"hashtable"
127.0.0.1:6379> SMEMBERS tags:sport
1) "volleyball"
2) "basketball"
3) "football"

這個 tags:sport 集合對象的空間結構可能如下圖:

這裏寫圖片描述

集合類型封裝的API

/* Set data type */
// 創建一個保存value的集合
robj *setTypeCreate(robj *value);
// 向subject集合中添加value,添加成功返回1,如果已經存在返回0
int setTypeAdd(robj *subject, robj *value);
// 從集合對象中刪除一個值爲value的元素,刪除成功返回1,失敗返回0
int setTypeRemove(robj *subject, robj *value);
// 集合中是否存在值爲value的元素,存在返回1,否則返回0
int setTypeIsMember(robj *subject, robj *value);
// 創建並初始化一個集合類型的迭代器
setTypeIterator *setTypeInitIterator(robj *subject);
// 釋放迭代器空間
void setTypeReleaseIterator(setTypeIterator *si);
// 將當前迭代器指向的元素保存在objele或llele中,迭代完畢返回-1
// 返回的對象的引用技術不增加,支持 讀時共享寫時複製
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele);
// 返回迭代器當前指向的元素對象的地址,需要手動釋放返回的對象
robj *setTypeNextObject(setTypeIterator *si);
// 從集合中隨機取出一個對象,保存在參數中
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele);
unsigned long setTypeRandomElements(robj *set, unsigned long count, robj *aux_set);
// 返回集合的元素數量
unsigned long setTypeSize(robj *subject);
// 將集合對象的INTSET編碼類型轉換爲enc類型
void setTypeConvert(robj *subject, int enc);

所有API註釋:集合類型實現源碼註釋

集合類型實現了自己的迭代器,也基於字典的迭代器封裝的

/* Structure to hold set iteration abstraction. */
typedef struct {
    robj *subject;                  //所屬的集合對象
    int encoding;                   //集合對象編碼類型
    int ii; /* intset iterator */   //整數集合的迭代器,編碼爲INTSET使用
    dictIterator *di;               //字典的迭代器,編碼爲HT使用
} setTypeIterator;

集合類型的迭代器的操作:

  • 創建並初始化一個集合類型的迭代器
// 創建並初始化一個集合類型的迭代器
setTypeIterator *setTypeInitIterator(robj *subject) {
    // 分配空間並初始化成員
    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;
}
  • 釋放集合類型的迭代器
// 釋放迭代器空間
void setTypeReleaseIterator(setTypeIterator *si) {
    // 如果是字典類型,需要先釋放字典類型的迭代器
    if (si->encoding == OBJ_ENCODING_HT)
        dictReleaseIterator(si->di);
    zfree(si);
}
  • 迭代操作分爲兩種
    • 將當前迭代器指向的對象保存在參數中,支持讀時共享寫時複製:setTypeNext()函數
    • 將當前迭代器指向的對象作爲返回值,不支持讀時共享寫時複製,需要釋放返回的對象:setTypeNextObject()函數
// 將當前迭代器指向的元素保存在objele或llele中,迭代完畢返回-1
// 返回的對象的引用計數不增加,支持 讀時共享寫時複製
int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
    // 迭代字典
    if (si->encoding == OBJ_ENCODING_HT) {
        // 得到下一個節點地址,更新迭代器
        dictEntry *de = dictNext(si->di);
        if (de == NULL) return -1;
        // 保存元素
        *objele = dictGetKey(de);
        *llele = -123456789; /* Not needed. Defensive. */
    // 迭代整數集合
    } else if (si->encoding == OBJ_ENCODING_INTSET) {
        // 從intset中保存元素到llele中
        if (!intsetGet(si->subject->ptr,si->ii++,llele))
            return -1;
        *objele = NULL; /* Not needed. Defensive. */
    } else {
        serverPanic("Wrong set encoding in setTypeNext");
    }
    return si->encoding;    //返回編碼類型
}

// 返回迭代器當前指向的元素對象的地址,需要手動釋放返回的對象
robj *setTypeNextObject(setTypeIterator *si) {
    int64_t intele;
    robj *objele;
    int encoding;

    // 得到當前集合對象的編碼類型
    encoding = setTypeNext(si,&objele,&intele);
    switch(encoding) {
        case -1:    return NULL;    //迭代完成
        case OBJ_ENCODING_INTSET:   //整數集合返回一個字符串類型的對象
            return createStringObjectFromLongLong(intele);
        case OBJ_ENCODING_HT:       //字典集合,返回共享的該對象
            incrRefCount(objele);
            return objele;
        default:
            serverPanic("Unsupported encoding");
    }
    return NULL; /* just to suppress warnings */
}

3. 集合鍵命令的實現

集合鍵的命令大部分根據編碼判斷出數據類型,然後調用相對應數據結構的API就可以實現,但是集合鍵命令有幾個我們重點分析,其他沒有分析的請上github上查看源碼的註釋:redis 集合鍵命令源碼註釋

  • 交集命令底層實現
// SINTER key [key ...]
// SINTERSTORE destination key [key ...]
// SINTER、SINTERSTORE一類命令的底層實現
void sinterGenericCommand(client *c, robj **setkeys,
                          unsigned long setnum, robj *dstkey) {
    // 分配存儲集合的數組
    robj **sets = zmalloc(sizeof(robj*)*setnum);
    setTypeIterator *si;
    robj *eleobj, *dstset = NULL;
    int64_t intobj;
    void *replylen = NULL;
    unsigned long j, cardinality = 0;
    int encoding;

    // 遍歷集合數組
    for (j = 0; j < setnum; j++) {
        // 如果dstkey爲空,則是SINTER命令,不爲空則是SINTERSTORE命令
        // 如果是SINTER命令,則以讀操作讀取出集合對象,否則以寫操作讀取出集合對象
        robj *setobj = dstkey ?
            lookupKeyWrite(c->db,setkeys[j]) :
            lookupKeyRead(c->db,setkeys[j]);

        // 讀取的集合對象不存在,執行清理操作
        if (!setobj) {
            zfree(sets);    //釋放集合數組空間
            // 如果是SINTERSTORE命令
            if (dstkey) {
                // 從數據庫中刪除存儲的目標集合對象dstkey
                if (dbDelete(c->db,dstkey)) {
                    // 發送信號表示數據庫鍵被修改,並更新髒鍵
                    signalModifiedKey(c->db,dstkey);
                    server.dirty++;
                }
                addReply(c,shared.czero);   //發送0給client
            // 如果是SINTER命令,發送空回覆
            } else {
                addReply(c,shared.emptymultibulk);
            }
            return;
        }

        // 讀取集合對象成功,檢查其數據類型
        if (checkType(c,setobj,OBJ_SET)) {
            zfree(sets);
            return;
        }
        // 將讀取出的對象保存在集合數組中
        sets[j] = setobj;
    }
    /* Sort sets from the smallest to largest, this will improve our
     * algorithm's performance */
    // 從小到大排序集合數組中的集合大小,能夠提高算法的性能
    qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);

    /* The first thing we should output is the total number of elements...
     * since this is a multi-bulk write, but at this stage we don't know
     * the intersection set size, so we use a trick, append an empty object
     * to the output list and save the pointer to later modify it with the
     * right length */
    // 首先我們應該輸出集合中元素的數量,但是現在不知道交集的大小
    // 因此創建一個空對象的鏈表,然後保存所有的回覆
    if (!dstkey) {
        replylen = addDeferredMultiBulkLength(c);   // STINER命令創建一個鏈表
    } else {
        /* If we have a target key where to store the resulting set
         * create this key with an empty set inside */
        dstset = createIntsetObject();              //STINERSTORE命令創建要給整數集合對象
    }

    /* Iterate all the elements of the first (smallest) set, and test
     * the element against all the other sets, if at least one set does
     * not include the element it is discarded */
    // 迭代第一個也是集合元素數量最小的集合的每一個元素,將該集合中的所有元素和其他集合作比較
    // 如果至少有一個集合不包括該元素,則該元素不屬於交集
    si = setTypeInitIterator(sets[0]);
    // 創建集合類型的迭代器並迭代器集合數組中的第一個集合的所有元素
    while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
        // 遍歷其他集合
        for (j = 1; j < setnum; j++) {

            // 跳過與第一個集合相等的集合,沒有必要比較兩個相同集合的元素,而且第一個集合作爲結果的交集
            if (sets[j] == sets[0]) continue;
            // 當前元素爲INTSET類型
            if (encoding == OBJ_ENCODING_INTSET) {
                /* intset with intset is simple... and fast */
                // 如果在當前intset集合中沒有找到該元素則直接跳過當前元素,操作下一個元素
                if (sets[j]->encoding == OBJ_ENCODING_INTSET &&
                    !intsetFind((intset*)sets[j]->ptr,intobj))
                {
                    break;
                /* in order to compare an integer with an object we
                 * have to use the generic function, creating an object
                 * for this */
                // 在字典中查找
                } else if (sets[j]->encoding == OBJ_ENCODING_HT) {
                    // 創建字符串對象
                    eleobj = createStringObjectFromLongLong(intobj);
                    // 如果當前元素不是當前集合中的元素,則釋放字符串對象跳過for循環體,操作下一個元素
                    if (!setTypeIsMember(sets[j],eleobj)) {
                        decrRefCount(eleobj);
                        break;
                    }
                    decrRefCount(eleobj);
                }
            // 當前元素爲HT字典類型
            } else if (encoding == OBJ_ENCODING_HT) {
                /* Optimization... if the source object is integer
                 * encoded AND the target set is an intset, we can get
                 * a much faster path. */
                // 當前元素的編碼是int類型且當前集合爲整數集合,如果該集合不包含該元素,則跳過循環
                if (eleobj->encoding == OBJ_ENCODING_INT &&
                    sets[j]->encoding == OBJ_ENCODING_INTSET &&
                    !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
                {
                    break;
                /* else... object to object check is easy as we use the
                 * type agnostic API here. */
                // 其他類型,在當前集合中查找該元素是否存在
                } else if (!setTypeIsMember(sets[j],eleobj)) {
                    break;
                }
            }
        }

        /* Only take action when all sets contain the member */
        // 執行到這裏,該元素爲結果集合中的元素
        if (j == setnum) {
            // 如果是SINTER命令,回覆集合
            if (!dstkey) {
                if (encoding == OBJ_ENCODING_HT)
                    addReplyBulk(c,eleobj);
                else
                    addReplyBulkLongLong(c,intobj);
                cardinality++;

            // 如果是SINTERSTORE命令,先將結果添加到集合中,因爲還要store到數據庫中
            } else {
                if (encoding == OBJ_ENCODING_INTSET) {
                    eleobj = createStringObjectFromLongLong(intobj);
                    setTypeAdd(dstset,eleobj);
                    decrRefCount(eleobj);
                } else {
                    setTypeAdd(dstset,eleobj);
                }
            }
        }
    }
    setTypeReleaseIterator(si); //釋放迭代器

    // SINTERSTORE命令,要將結果的集合添加到數據庫中
    if (dstkey) {
        /* Store the resulting set into the target, if the intersection
         * is not an empty set. */
        // 如果之前存在該集合則先刪除
        int deleted = dbDelete(c->db,dstkey);
        // 結果集大小非空,則將其添加到數據庫中
        if (setTypeSize(dstset) > 0) {
            dbAdd(c->db,dstkey,dstset);
            // 回覆結果集的大小
            addReplyLongLong(c,setTypeSize(dstset));
            // 發送"sinterstore"事件通知
            notifyKeyspaceEvent(NOTIFY_SET,"sinterstore",
                dstkey,c->db->id);
        // 結果集爲空,釋放空間
        } else {
            decrRefCount(dstset);
            // 發送0給client
            addReply(c,shared.czero);
            // 發送"del"事件通知
            if (deleted)
                notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
                    dstkey,c->db->id);
        }
        // 鍵被修改,發送信號。更新髒鍵
        signalModifiedKey(c->db,dstkey);
        server.dirty++;

    // SINTER命令,回覆結果集合給client
    } else {
        setDeferredMultiBulkLength(c,replylen,cardinality);
    }
    zfree(sets);    //釋放集合數組空間
}
  • 差集和並集命令的底層實現

計算差集給出了兩個算法,使用於不同的場景。

  1. 時間複雜度O(N*M),N是第一個集合中元素的總個數,M是集合的總個數
  2. 時間複雜度O(N),N是所有集合中元素的總個數
#define SET_OP_UNION 0      //並集
#define SET_OP_DIFF 1       //差集
#define SET_OP_INTER 2      //交集

// SUNION key [key ...]
// SUNIONSTORE destination key [key ...]
// SDIFF key [key ...]
// SDIFFSTORE destination key [key ...]
// 並集、差集命令的底層實現
void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
                              robj *dstkey, int op) {
    //分配集合數組的空間
    robj **sets = zmalloc(sizeof(robj*)*setnum);
    setTypeIterator *si;
    robj *ele, *dstset = NULL;
    int j, cardinality = 0;
    int diff_algo = 1;

    // 遍歷數組中集合鍵對象
    for (j = 0; j < setnum; j++) {
        // 如果dstkey爲空,則是SUNION或SDIFF命令,不爲空則是SUNIONSTORE或SDIFFSTORE命令
        // 如果是SUNION或SDIFF命令,則以讀操作讀取出集合對象,否則以寫操作讀取出集合對象
        robj *setobj = dstkey ?
            lookupKeyWrite(c->db,setkeys[j]) :
            lookupKeyRead(c->db,setkeys[j]);
        // 不存在的集合鍵設置爲空
        if (!setobj) {
            sets[j] = NULL;
            continue;
        }
        // 檢查存在的集合鍵是否是集合對象,不是則釋放空間
        if (checkType(c,setobj,OBJ_SET)) {
            zfree(sets);
            return;
        }
        sets[j] = setobj;   //保存到集合數組中
    }

    /* Select what DIFF algorithm to use.
     *
     * Algorithm 1 is O(N*M) where N is the size of the element first set
     * and M the total number of sets.
     *
     * Algorithm 2 is O(N) where N is the total number of elements in all
     * the sets.
     *
     * We compute what is the best bet with the current input here. */
    // 計算差集共有兩種算法
    // 1.時間複雜度O(N*M),N是第一個集合中元素的總個數,M是集合的總個數
    // 2.時間複雜度O(N),N是所有集合中元素的總個數
    if (op == SET_OP_DIFF && sets[0]) {
        long long algo_one_work = 0, algo_two_work = 0;

        // 遍歷集合數組
        for (j = 0; j < setnum; j++) {
            if (sets[j] == NULL) continue;

            // 計算sets[0] × setnum的值
            algo_one_work += setTypeSize(sets[0]);
            // 計算所有集合的元素總個數
            algo_two_work += setTypeSize(sets[j]);
        }

        /* Algorithm 1 has better constant times and performs less operations
         * if there are elements in common. Give it some advantage. */
        algo_one_work /= 2;
        //根據algo_one_work和algo_two_work選擇不同算法
        diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;

        // 如果是算法1,M較小,執行操作少
        if (diff_algo == 1 && setnum > 1) {
            /* With algorithm 1 it is better to order the sets to subtract
             * by decreasing size, so that we are more likely to find
             * duplicated elements ASAP. */
            // 將集合數組除第一個集合以外的所有集合,按照集合的元素排序
            qsort(sets+1,setnum-1,sizeof(robj*),
                qsortCompareSetsByRevCardinality);
        }
    }

    /* We need a temp set object to store our union. If the dstkey
     * is not NULL (that is, we are inside an SUNIONSTORE operation) then
     * this set object will be the resulting object to set into the target key*/
    // 創建一個臨時集合對象作爲結果集
    dstset = createIntsetObject();

    // 執行並集操作
    if (op == SET_OP_UNION) {
        /* Union is trivial, just add every element of every set to the
         * temporary set. */
        // 僅僅講每一個集合中的每一個元素加入到結果集中
        // 遍歷每一個集合
        for (j = 0; j < setnum; j++) {
            if (!sets[j]) continue; /* non existing keys are like empty sets */

            // 創建一個集合類型的迭代器
            si = setTypeInitIterator(sets[j]);
            // 遍歷當前集合中的所有元素
            while((ele = setTypeNextObject(si)) != NULL) {
                // 講迭代器指向的當前元素對象加入到結果集中
                if (setTypeAdd(dstset,ele)) cardinality++;  //如果結果集中不存在新加入的元素,則更新結果集的元素個數計數器
                decrRefCount(ele);  //否則直接釋放元素對象空間
            }
            setTypeReleaseIterator(si);     //釋放迭代器空間
        }
    // 執行差集操作並且使用算法1
    } else if (op == SET_OP_DIFF && sets[0] && diff_algo == 1) {
        /* DIFF Algorithm 1:
         *
         * We perform the diff by iterating all the elements of the first set,
         * and only adding it to the target set if the element does not exist
         * into all the other sets.
         *
         * This way we perform at max N*M operations, where N is the size of
         * the first set, and M the number of sets. */
        // 我們執行差集操作通過遍歷第一個集合中的所有元素,並且將其他集合中不存在元素加入到結果集中
        // 時間複雜度O(N*M),N是第一個集合中元素的總個數,M是集合的總個數
        si = setTypeInitIterator(sets[0]);
        // 創建集合類型迭代器遍歷第一個集合中的所有元素
        while((ele = setTypeNextObject(si)) != NULL) {
            // 遍歷集合數組中的除了第一個的所有集合,檢查元素是否存在在每一個集合
            for (j = 1; j < setnum; j++) {
                if (!sets[j]) continue; /* no key is an empty set. */   //集合鍵不存在跳過本次循環
                if (sets[j] == sets[0]) break; /* same set! */          //相同的集合沒必要比較
                if (setTypeIsMember(sets[j],ele)) break;                //如果元素存在後面的集合中,遍歷下一個元素
            }
            // 執行到這裏,說明當前元素不存在於 除了第一個的所有集合
            if (j == setnum) {
                /* There is no other set with this element. Add it. */
                // 因此將當前元素添加到結果集合中,更新計數器
                setTypeAdd(dstset,ele);
                cardinality++;
            }
            decrRefCount(ele);  //釋放元素對象空間
        }
        setTypeReleaseIterator(si); //釋放迭代器空間
    // 執行差集操作並且使用算法2
    } else if (op == SET_OP_DIFF && sets[0] && diff_algo == 2) {
        /* DIFF Algorithm 2:
         *
         * Add all the elements of the first set to the auxiliary set.
         * Then remove all the elements of all the next sets from it.
         *
         * This is O(N) where N is the sum of all the elements in every
         * set. */
        // 將第一個集合的所有元素加入到結果集中,然後遍歷其後的所有集合,將有交集的元素從結果集中刪除
        // 2.時間複雜度O(N),N是所有集合中元素的總個數
        // 遍歷所有的集合
        for (j = 0; j < setnum; j++) {
            if (!sets[j]) continue; /* non existing keys are like empty sets */

            si = setTypeInitIterator(sets[j]);
            // 創建集合類型迭代器遍歷每一個集合中的所有元素
            while((ele = setTypeNextObject(si)) != NULL) {
                // 如果是第一個集合,將每一個元素加入到結果集中
                if (j == 0) {
                    if (setTypeAdd(dstset,ele)) cardinality++;
                // 如果是其後的集合,將當前元素從結果集中刪除,如結果集中有的話
                } else {
                    if (setTypeRemove(dstset,ele)) cardinality--;
                }
                decrRefCount(ele);
            }
            setTypeReleaseIterator(si);//釋放迭代器空間

            /* Exit if result set is empty as any additional removal
             * of elements will have no effect. */
            // 只要結果集爲空,那麼差集結果就爲空,不用比較後續的集合
            if (cardinality == 0) break;
        }
    }

    /* Output the content of the resulting set, if not in STORE mode */
    // 如果不是STORE一類的命令,輸出所有的結果
    if (!dstkey) {
        // 發送結果集的元素個數給client
        addReplyMultiBulkLen(c,cardinality);

        // 遍歷結果集中的每一個元素,併發送給client
        si = setTypeInitIterator(dstset);
        while((ele = setTypeNextObject(si)) != NULL) {
            addReplyBulk(c,ele);
            decrRefCount(ele);  //發送完要釋放空間
        }
        setTypeReleaseIterator(si); //釋放迭代器
        decrRefCount(dstset);       //發送集合後要釋放結果集的空間

    // STORE一類的命令,輸出所有的結果
    } else {
        /* If we have a target key where to store the resulting set
         * create this key with the result set inside */
        // 先將目標集合從數據庫中刪除,如果存在的話
        int deleted = dbDelete(c->db,dstkey);
        // 如果結果集合非空
        if (setTypeSize(dstset) > 0) {
            dbAdd(c->db,dstkey,dstset); //將結果集加入到數據庫中
            addReplyLongLong(c,setTypeSize(dstset));    //發送結果集的元素個數給client
            // 發送對應的事件通知
            notifyKeyspaceEvent(NOTIFY_SET,
                op == SET_OP_UNION ? "sunionstore" : "sdiffstore",
                dstkey,c->db->id);

        // 結果集爲空,則釋放空間
        } else {
            decrRefCount(dstset);
            addReply(c,shared.czero);   //發送0給client
            // 發送"del"事件通知
            if (deleted)
                notifyKeyspaceEvent(NOTIFY_GENERIC,"del",
                    dstkey,c->db->id);
        }
        // 鍵被修改,發送信號通知,更新髒鍵
        signalModifiedKey(c->db,dstkey);
        server.dirty++;
    }
    zfree(sets);    //釋放集合數組空間
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章