1. 程式人生 > >Redis之雜湊物件原始碼閱讀

Redis之雜湊物件原始碼閱讀

hashTypeTryConversion:對傳入的引數進行檢查是否需要從ziplist轉換成hashtable

void hashTypeTryConversion(robj *o, robj **argv, int start, int end) {
    int i;

    // 如果物件不是 ziplist 編碼,那麼直接返回
    if (o->encoding != REDIS_ENCODING_ZIPLIST) return;

    // 檢查所有輸入物件,看它們的字串值是否超過了指定長度
    for (i = start; i <= end; i++) {
        if (sdsEncodedObject(argv[i]) &&
            sdslen(argv[i]->ptr) > server.hash_max_ziplist_value)
        {
            // 將物件的編碼轉換成 REDIS_ENCODING_HT
            hashTypeConvert(o, REDIS_ENCODING_HT);
            break;
        }
    }
}

hashTypeTryObjectEncoding:當subject為hashtable編碼時,嘗試將o1,o2進行編碼

void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2) {
    if (subject->encoding == REDIS_ENCODING_HT) {
        if (o1) *o1 = tryObjectEncoding(*o1);
        if (o2) *o2 = tryObjectEncoding(*o2);
    }
}

hashTypeGetFromZiplist:從ziplist找到field對應的值

int hashTypeGetFromZiplist(robj *o, robj *field,
                           unsigned char **vstr,
                           unsigned int *vlen,
                           long long *vll)
{
    unsigned char *zl, *fptr = NULL, *vptr = NULL;
    int ret;

    // 確保編碼正確
    redisAssert(o->encoding == REDIS_ENCODING_ZIPLIST);

    // 取出未編碼的域
    field = getDecodedObject(field);

    // 遍歷 ziplist ,查詢域的位置
    zl = o->ptr;
    fptr = ziplistIndex(zl, ZIPLIST_HEAD);
    if (fptr != NULL) {
        // 定位包含域的節點
        fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
        if (fptr != NULL) {
            /* Grab pointer to the value (fptr points to the field) */
            // 域已經找到,取出和它相對應的值的位置
            vptr = ziplistNext(zl, fptr);
            redisAssert(vptr != NULL);
        }
    }

    decrRefCount(field);

    // 從 ziplist 節點中取出值
    if (vptr != NULL) {
        ret = ziplistGet(vptr, vstr, vlen, vll);
        redisAssert(ret);
        return 0;
    }

    // 沒找到
    return -1;
}

hashTypeGetFromHashTable:從hashtable中找到鍵對應的值

int hashTypeGetFromHashTable(robj *o, robj *field, robj **value) {
    dictEntry *de;

    // 確保編碼正確
    redisAssert(o->encoding == REDIS_ENCODING_HT);

    // 在字典中查詢域(鍵)
    de = dictFind(o->ptr, field);

    // 鍵不存在
    if (de == NULL) return -1;

    // 取出域(鍵)的值
    *value = dictGetVal(de);

    // 成功找到
    return 0;
}

hashTypeGetObject:這是一個多型函式,根據雜湊物件是ziplist還是hashtable編碼採用不同的獲取方式

robj *hashTypeGetObject(robj *o, robj *field) {
    robj *value = NULL;

    // 從 ziplist 中取出值
    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) {
            // 建立值物件
            if (vstr) {
                value = createStringObject((char*)vstr, vlen);
            } else {
                value = createStringObjectFromLongLong(vll);
            }
        }

    // 從字典中取出值
    } else if (o->encoding == REDIS_ENCODING_HT) {
        robj *aux;

        if (hashTypeGetFromHashTable(o, field, &aux) == 0) {
            incrRefCount(aux);
            value = aux;
        }

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

    // 返回值物件,或者 NULL
    return value;
}

hashTypeExists:檢查給定的鍵是否在當前雜湊物件中

int hashTypeExists(robj *o, robj *field) {

    // 檢查 ziplist
    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
        unsigned char *vstr = NULL;
        unsigned int vlen = UINT_MAX;
        long long vll = LLONG_MAX;

        if (hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll) == 0) return 1;

    // 檢查字典
    } else if (o->encoding == REDIS_ENCODING_HT) {
        robj *aux;

        if (hashTypeGetFromHashTable(o, field, &aux) == 0) return 1;

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

    // 不存在
    return 0;
}

hashTypeSet:將指定的鍵值對新增到雜湊物件中

int hashTypeSet(robj *o, robj *field, robj *value) {
    int update = 0;

    // 新增到 ziplist
    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
        unsigned char *zl, *fptr, *vptr;

        // 解碼成字串或者數字
        field = getDecodedObject(field);
        value = getDecodedObject(value);

        // 遍歷整個 ziplist ,嘗試查詢並更新 field (如果它已經存在的話)
        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {
            // 定位到域 field
            fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
            if (fptr != NULL) {
                /* Grab pointer to the value (fptr points to the field) */
                // 定位到域的值
                vptr = ziplistNext(zl, fptr);
                redisAssert(vptr != NULL);

                // 標識這次操作為更新操作
                update = 1;

                /* Delete value */
                // 刪除舊的鍵值對
                zl = ziplistDelete(zl, &vptr);

                /* Insert new value */
                // 新增新的鍵值對
                zl = ziplistInsert(zl, vptr, value->ptr, sdslen(value->ptr));
            }
        }

        // 如果這不是更新操作,那麼這就是一個新增操作
        if (!update) {
            /* Push new field/value pair onto the tail of the ziplist */
            // 將新的 field-value 對推入到 ziplist 的末尾
            zl = ziplistPush(zl, field->ptr, sdslen(field->ptr), ZIPLIST_TAIL);
            zl = ziplistPush(zl, value->ptr, sdslen(value->ptr), ZIPLIST_TAIL);
        }
        
        // 更新物件指標
        o->ptr = zl;

        // 釋放臨時物件
        decrRefCount(field);
        decrRefCount(value);

        /* Check if the ziplist needs to be converted to a hash table */
        // 檢查在新增操作完成之後,是否需要將 ZIPLIST 編碼轉換成 HT 編碼
        if (hashTypeLength(o) > server.hash_max_ziplist_entries)
            hashTypeConvert(o, REDIS_ENCODING_HT);

    // 新增到字典
    } else if (o->encoding == REDIS_ENCODING_HT) {

        // 新增或替換鍵值對到字典
        // 新增返回 1 ,替換返回 0
        if (dictReplace(o->ptr, field, value)) { /* Insert */
            incrRefCount(field);
        } else { /* Update */
            update = 1;
        }

        incrRefCount(value);
    } else {
        redisPanic("Unknown hash encoding");
    }

    // 更新/新增指示變數
    return update;
}

hashTypeDelete:從雜湊物件中刪除指定的鍵值對

int hashTypeDelete(robj *o, robj *field) {
    int deleted = 0;

    // 從 ziplist 中刪除
    if (o->encoding == REDIS_ENCODING_ZIPLIST) {
        unsigned char *zl, *fptr;

        field = getDecodedObject(field);

        zl = o->ptr;
        fptr = ziplistIndex(zl, ZIPLIST_HEAD);
        if (fptr != NULL) {
            // 定位到域
            fptr = ziplistFind(fptr, field->ptr, sdslen(field->ptr), 1);
            if (fptr != NULL) {
                // 刪除域和值
                zl = ziplistDelete(zl,&fptr);
                zl = ziplistDelete(zl,&fptr);
                o->ptr = zl;
                deleted = 1;
            }
        }

        decrRefCount(field);

    // 從字典中刪除
    } else if (o->encoding == REDIS_ENCODING_HT) {
        if (dictDelete((dict*)o->ptr, field) == REDIS_OK) {
            deleted = 1;

            /* Always check if the dictionary needs a resize after a delete. */
            // 刪除成功時,看字典是否需要收縮
            if (htNeedsResize(o->ptr)) dictResize(o->ptr);
        }

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

    return deleted;
}

hsetCommand:hset命令

void hsetCommand(redisClient *c) {
    int update;
    robj *o;

    // 取出或新建立雜湊物件
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;

    // 如果需要的話,轉換雜湊物件的編碼
    hashTypeTryConversion(o,c->argv,2,3);

    // 編碼 field 和 value 物件以節約空間
    hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);

    // 設定 field 和 value 到 hash
    update = hashTypeSet(o,c->argv[2],c->argv[3]);

    // 返回狀態:顯示 field-value 對是新新增還是更新
    addReply(c, update ? shared.czero : shared.cone);

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

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

    // 將伺服器設為髒
    server.dirty++;
}

hsetnxCommand:hsetnx命令

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

    // 取出或新建立雜湊物件
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;

    // 如果需要的話,轉換雜湊物件的編碼
    hashTypeTryConversion(o,c->argv,2,3);

    // 如果 field-value 對已經存在
    // 那麼回覆 0 
    if (hashTypeExists(o, c->argv[2])) {
        addReply(c, shared.czero);

    // 否則,設定 field-value 對
    } else {
        // 對 field 和 value 物件編碼,以節省空間
        hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]);
        // 設定
        hashTypeSet(o,c->argv[2],c->argv[3]);

        // 回覆 1 ,表示設定成功
        addReply(c, shared.cone);

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

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

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

hmsetCommand:新增多個鍵值對的命令

void hmsetCommand(redisClient *c) {
    int i;
    robj *o;

    // field-value 引數必須成對出現
    if ((c->argc % 2) == 1) {
        addReplyError(c,"wrong number of arguments for HMSET");
        return;
    }

    // 取出或新建立雜湊物件
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;

    // 如果需要的話,轉換雜湊物件的編碼
    hashTypeTryConversion(o,c->argv,2,c->argc-1);

    // 遍歷並設定所有 field-value 對
    for (i = 2; i < c->argc; i += 2) {
        // 編碼 field-value 對,以節約空間
        hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]);
        // 設定
        hashTypeSet(o,c->argv[i],c->argv[i+1]);
    }

    // 向客戶端傳送回覆
    addReply(c, shared.ok);

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

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

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

hincrbyCommand:為指定鍵的值進行增

void hincrbyCommand(redisClient *c) {
    long long value, incr, oldvalue;
    robj *o, *current, *new;

    // 取出 incr 引數的值,並建立物件
    if (getLongLongFromObjectOrReply(c,c->argv[3],&incr,NULL) != REDIS_OK) return;

    // 取出或新建立雜湊物件
    if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return;

    // 取出 field 的當前值
    if ((current = hashTypeGetObject(o,c->argv[2])) != NULL) {
        // 取出值的整數表示
        if (getLongLongFromObjectOrReply(c,current,&value,
            "hash value is not an integer") != REDIS_OK) {
            decrRefCount(current);
            return;
        }
        decrRefCount(current);
    } else {
        // 如果值當前不存在,那麼預設為 0
        value = 0;
    }

    // 檢查計算是否會造成溢位
    oldvalue = value;
    if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
        (incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
        addReplyError(c,"increment or decrement would overflow");
        return;
    }

    // 計算結果
    value += incr;
    // 為結果建立新的值物件
    new = createStringObjectFromLongLong(value);
    // 編碼值物件
    hashTypeTryObjectEncoding(o,&c->argv[2],NULL);
    // 關聯鍵和新的值物件,如果已經有物件存在,那麼用新物件替換它
    hashTypeSet(o,c->argv[2],new);
    decrRefCount(new);

    // 將計算結果用作回覆
    addReplyLongLong(c,value);

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

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

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

hgetCommand:hget命令

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

    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,o,REDIS_HASH)) return;

    // 取出並返回域的值
    addHashFieldToReply(c, o, c->argv[2]);
}

hdelCommand:hdel方法

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

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

    // 刪除指定域值對
    for (j = 2; j < c->argc; j++) {
        if (hashTypeDelete(o,c->argv[j])) {

            // 成功刪除一個域值對時進行計數
            deleted++;

            // 如果雜湊已經為空,那麼刪除這個物件
            if (hashTypeLength(o) == 0) {
                dbDelete(c->db,c->argv[1]);
                keyremoved = 1;
                break;
            }
        }
    }

    // 只要有至少一個域值對被修改了,那麼執行以下程式碼
    if (deleted) {
        // 傳送鍵修改訊號
        signalModifiedKey(c->db,c->argv[1]);

        // 傳送事件通知
        notifyKeyspaceEvent(REDIS_NOTIFY_HASH,"hdel",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);
}