Redis源码阅读之hget命令

Redis源码阅读之hget命令

June 22, 2024
Redis
Redis
Redis版本:7.0.2

hget命令对应的函数为hgetCommand,源码如下:

****************t_hash.c*******************

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

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

    addHashFieldToReply(c, o, c->argv[2]->ptr);
}

static void addHashFieldToReply(client *c, robj *o, sds field) {
    if (o == NULL) {
        addReplyNull(c);
        return;
    }

    unsigned char *vstr = NULL;
    unsigned int vlen = UINT_MAX;
    long long vll = LLONG_MAX;

    if (hashTypeGetValue(o, field, &vstr, &vlen, &vll) == C_OK) {
        if (vstr) {
            addReplyBulkCBuffer(c, vstr, vlen);
        } else {
            addReplyBulkLongLong(c, vll);
        }
    } else {
        addReplyNull(c);
    }
}

/* Higher level function of hashTypeGet*() that returns the hash value
 * associated with the specified field. If the field is found C_OK
 * is returned, otherwise C_ERR. The returned object is returned by
 * reference in either *vstr and *vlen if it's returned in string form,
 * or stored in *vll if it's returned as a number.
 * hashTypGet*()的高级函数,返回与指定字段关联的哈希值。如果找到字段,返回C_OK,否则返回C_ERR。
 * 如果返回的是字符串形式,则以*vstr和*vlen的引用的方式返回对象。如果返回的是数字,则存储在*vll中。
 *
 * If *vll is populated *vstr is set to NULL, so the caller
 * can always check the function return by checking the return value
 * for C_OK and checking if vll (or vstr) is NULL. 
 * 如果*vll被填充,*vstr被设置为NULL,调用者就可以通过检查检查返回值是否为C_OK以及vll(或vstr)是否为NULL来检查函数返回。
 * */
int hashTypeGetValue(robj *o, sds field, unsigned char **vstr, unsigned int *vlen, long long *vll) {
    if (o->encoding == OBJ_ENCODING_LISTPACK) {
        *vstr = NULL;
        if (hashTypeGetFromListpack(o, field, vstr, vlen, vll) == 0)
            return C_OK;
    } else if (o->encoding == OBJ_ENCODING_HT) {
        sds value;
        if ((value = hashTypeGetFromHashTable(o, field)) != NULL) {
            *vstr = (unsigned char*) value;
            *vlen = sdslen(value);
            return C_OK;
        }
    } else {
        serverPanic("Unknown hash encoding");
    }
    return C_ERR;
}

/* Get the value from a hash table encoded hash, identified by field.
 * Returns NULL when the field cannot be found, otherwise the SDS value
 * is returned. 
 * 从一个使用hash表存储的hash中获取值,由field标识。当找不到field时返回NULL,否则返回SDS值。
 * */
sds hashTypeGetFromHashTable(robj *o, sds field) {
    dictEntry *de;

    serverAssert(o->encoding == OBJ_ENCODING_HT);

    de = dictFind(o->ptr, field);
    if (de == NULL) return NULL;
    return dictGetVal(de);
}

**********************dict.c************************
dictEntry *dictFind(dict *d, const void *key)
{
    dictEntry *he;
    uint64_t h, idx, table;

    if (dictSize(d) == 0)
        return NULL; /* dict is empty */
    if (dictIsRehashing(d))
        _dictRehashStep(d);
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++)
    {
        idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
        he = d->ht_table[table][idx];
        while (he)
        {
            if (key == he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d))
            return NULL;
    }
    return NULL;
}

可以看到,查找的主要逻辑为:

  1. 调用lookupKeyReadOrReply,在db的全局字典中查找key,如果不存在,或者类型不正确,则返回。
  2. 调用addHashFieldToReply函数,将hash中的某个字段返回。在这个函数中会调用hashTypeGetValue函数,在hashTypeGetValue中 会根据hash的编码类型(是使用listpack还是hashtable)来调用不同的函数来获取值。

最终,对于hashtable编码的hash,会调用dictFind函数来查找field对应的值。

而dictFind的主要逻辑为:

  1. 如果dict对象长度为0,则返回NULL。
  2. 如果正在进行rehash,则进行rehash。
  3. 遍历hash表。如果找到key后,则遍历链表。