1. 程式人生 > >Redis值集合物件原始碼閱讀

Redis值集合物件原始碼閱讀

setTypeCreate:返回一個集合物件

robj *setTypeCreate(robj *value) {

    if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
        return createIntsetObject();

    return createSetObject();
}

setTypeAdd:向集合中新增元素

int setTypeAdd(robj *subject, robj *value) {
    long long llval;

    // 字典
    if (subject->encoding == REDIS_ENCODING_HT) {
        // 將 value 作為鍵, NULL 作為值,將元素新增到字典中
        if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
            incrRefCount(value);
            return 1;
        }

    // intset
    } else if (subject->encoding == REDIS_ENCODING_INTSET) {
        
        // 如果物件的值可以編碼為整數的話,那麼將物件的值新增到 intset 中
        if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_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,REDIS_ENCODING_HT);
                return 1;
            }

        // 如果物件的值不能編碼為整數,那麼將集合從 intset 編碼轉換為 HT 編碼
        // 然後再執行新增操作
        } else {
            /* Failed to get integer from object, convert to regular set. */
            setTypeConvert(subject,REDIS_ENCODING_HT);

            /* The set *was* an intset and this value is not integer
             * encodable, so dictAdd should always work. */
            redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
            incrRefCount(value);
            return 1;
        }

    // 未知編碼
    } else {
        redisPanic("Unknown set encoding");
    }

    // 新增失敗,元素已經存在
    return 0;
}

setTypeRemove:刪除某個操作

int setTypeRemove(robj *setobj, robj *value) {
    long long llval;

    // HT
    if (setobj->encoding == REDIS_ENCODING_HT) {
        // 從字典中刪除鍵(集合元素)
        if (dictDelete(setobj->ptr,value) == DICT_OK) {
            // 看是否有必要在刪除之後縮小字典的大小
            if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
            return 1;
        }

    // INTSET
    } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
        // 如果物件的值可以編碼為整數的話,那麼嘗試從 intset 中移除元素
        if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
            int success;
            setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
            if (success) return 1;
        }

    // 未知編碼
    } else {
        redisPanic("Unknown set encoding");
    }

    // 刪除失敗
    return 0;
}

setTypeIsMember:檢查是否有某個元素

int setTypeIsMember(robj *subject, robj *value) {
    long long llval;

    // HT
    if (subject->encoding == REDIS_ENCODING_HT) {
        return dictFind((dict*)subject->ptr,value) != NULL;

    // INTSET
    } else if (subject->encoding == REDIS_ENCODING_INTSET) {
        if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
            return intsetFind((intset*)subject->ptr,llval);
        }

    // 未知編碼
    } else {
        redisPanic("Unknown set encoding");
    }

    // 查詢失敗
    return 0;
}

setTypeRandomElement:從集合中隨機獲取一個元素

int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {

    if (setobj->encoding == REDIS_ENCODING_HT) {
        dictEntry *de = dictGetRandomKey(setobj->ptr);
        *objele = dictGetKey(de);

    } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
        *llele = intsetRandom(setobj->ptr);

    } else {
        redisPanic("Unknown set encoding");
    }

    return setobj->encoding;
}

setTypeSize:返回集合中元素的個數

unsigned long setTypeSize(robj *subject) {

    if (subject->encoding == REDIS_ENCODING_HT) {
        return dictSize((dict*)subject->ptr);

    } else if (subject->encoding == REDIS_ENCODING_INTSET) {
        return intsetLen((intset*)subject->ptr);

    } else {
        redisPanic("Unknown set encoding");
    }
}

saddCommand:sadd命令

void saddCommand(redisClient *c) {
    robj *set;
    int j, added = 0;

    // 取出集合物件
    set = lookupKeyWrite(c->db,c->argv[1]);

    // 物件不存在,建立一個新的,並將它關聯到資料庫
    if (set == NULL) {
        set = setTypeCreate(c->argv[2]);
        dbAdd(c->db,c->argv[1],set);

    // 物件存在,檢查型別
    } else {
        if (set->type != REDIS_SET) {
            addReply(c,shared.wrongtypeerr);
            return;
        }
    }

    // 將所有輸入元素新增到集合中
    for (j = 2; j < c->argc; j++) {
        c->argv[j] = tryObjectEncoding(c->argv[j]);
        // 只有元素未存在於集合時,才算一次成功新增
        if (setTypeAdd(set,c->argv[j])) added++;
    }

    // 如果有至少一個元素被成功新增,那麼執行以下程式
    if (added) {
        // 傳送鍵修改訊號
        signalModifiedKey(c->db,c->argv[1]);
        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    }

    // 將資料庫設為髒
    server.dirty += added;

    // 返回新增元素的數量
    addReplyLongLong(c,added);
}

sremCommand:刪除多個值

void sremCommand(redisClient *c) {
    robj *set;
    int j, deleted = 0, keyremoved = 0;

    // 取出集合物件
    if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,set,REDIS_SET)) return;

    // 刪除輸入的所有元素
    for (j = 2; j < c->argc; j++) {
        
        // 只有元素在集合中時,才算一次成功刪除
        if (setTypeRemove(set,c->argv[j])) {
            deleted++;
            // 如果集合已經為空,那麼刪除集合物件
            if (setTypeSize(set) == 0) {
                dbDelete(c->db,c->argv[1]);
                keyremoved = 1;
                break;
            }
        }
    }

    // 如果有至少一個元素被成功刪除,那麼執行以下程式
    if (deleted) {
        // 傳送鍵修改訊號
        signalModifiedKey(c->db,c->argv[1]);
        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
        // 傳送事件通知
        if (keyremoved)
            notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
                                c->db->id);
        // 將資料庫設為髒
        server.dirty += deleted;
    }
    
    // 將被成功刪除元素的數量作為回覆
    addReplyLongLong(c,deleted);
}

smoveCommand:將指定元素從源集合移動到目的集合

void smoveCommand(redisClient *c) {
    robj *srcset, *dstset, *ele;

    // 取出源集合
    srcset = lookupKeyWrite(c->db,c->argv[1]);
    // 取出目標集合
    dstset = lookupKeyWrite(c->db,c->argv[2]);

    // 編碼元素
    ele = c->argv[3] = tryObjectEncoding(c->argv[3]);

    /* If the source key does not exist return 0 */
    // 源集合不存在,直接返回
    if (srcset == NULL) {
        addReply(c,shared.czero);
        return;
    }

    /* If the source key has the wrong type, or the destination key
     * is set and has the wrong type, return with an error. */
    // 如果源集合的型別錯誤
    // 或者目標集合存在、但是型別錯誤
    // 那麼直接返回
    if (checkType(c,srcset,REDIS_SET) ||
        (dstset && checkType(c,dstset,REDIS_SET))) return;

    /* If srcset and dstset are equal, SMOVE is a no-op */
    // 如果源集合和目標集合相等,那麼直接返回
    if (srcset == dstset) {
        addReply(c,shared.cone);
        return;
    }

    /* If the element cannot be removed from the src set, return 0. */
    // 從源集合中移除目標元素
    // 如果目標元素不存在於源集合中,那麼直接返回
    if (!setTypeRemove(srcset,ele)) {
        addReply(c,shared.czero);
        return;
    }

    // 傳送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);

    /* Remove the src set from the database when empty */
    // 如果源集合已經為空,那麼將它從資料庫中刪除
    if (setTypeSize(srcset) == 0) {
        // 刪除集合物件
        dbDelete(c->db,c->argv[1]);
        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
    }

    // 傳送鍵修改訊號
    signalModifiedKey(c->db,c->argv[1]);
    signalModifiedKey(c->db,c->argv[2]);

    // 將資料庫設為髒
    server.dirty++;

    /* Create the destination set when it doesn't exist */
    // 如果目標集合不存在,那麼建立它
    if (!dstset) {
        dstset = setTypeCreate(ele);
        dbAdd(c->db,c->argv[2],dstset);
    }

    /* An extra key has changed when ele was successfully added to dstset */
    // 將元素新增到目標集合
    if (setTypeAdd(dstset,ele)) {
        // 將資料庫設為髒
        server.dirty++;
        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
    }

    // 回覆 1 
    addReply(c,shared.cone);
}

sismemberCommand:檢查是否存在指定成員

void sismemberCommand(redisClient *c) {
    robj *set;

    // 取出集合物件
    if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,set,REDIS_SET)) return;

    // 編碼輸入元素
    c->argv[2] = tryObjectEncoding(c->argv[2]);

    // 檢查是否存在
    if (setTypeIsMember(set,c->argv[2]))
        addReply(c,shared.cone);
    else
        addReply(c,shared.czero);
}

scardCommand:獲取成員的集合數目

void scardCommand(redisClient *c) {
    robj *o;

    // 取出集合
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,REDIS_SET)) return;

    // 返回集合的基數
    addReplyLongLong(c,setTypeSize(o));
}

spopCommand:隨機彈出一個集合中的元素

void spopCommand(redisClient *c) {
    robj *set, *ele, *aux;
    int64_t llele;
    int encoding;

    // 取出集合
    if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,set,REDIS_SET)) return;

    // 從集合中隨機取出一個元素
    encoding = setTypeRandomElement(set,&ele,&llele);

    // 將被取出元素從集合中刪除
    if (encoding == REDIS_ENCODING_INTSET) {
        ele = createStringObjectFromLongLong(llele);
        set->ptr = intsetRemove(set->ptr,llele,NULL);
    } else {
        incrRefCount(ele);
        setTypeRemove(set,ele);
    }

    // 傳送事件通知
    notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);

    /* Replicate/AOF this command as an SREM operation */
    // 將這個命令當作 SREM 來傳播,防止產生有害的隨機性,導致資料不一致
    // (不同的伺服器隨機刪除不同的元素)
    aux = createStringObject("SREM",4);
    rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
    decrRefCount(ele);
    decrRefCount(aux);

    // 返回回覆
    addReplyBulk(c,ele);

    // 如果集合已經為空,那麼從資料庫中刪除它
    if (setTypeSize(set) == 0) {
        // 刪除集合
        dbDelete(c->db,c->argv[1]);
        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
    }

    // 傳送鍵修改訊號
    signalModifiedKey(c->db,c->argv[1]);

    // 將資料庫設為髒
    server.dirty++;
}