Redis的Hash Table实现总结

这篇博客详细总结了Redis中Hash Table的数据结构,包括从底层到上层的构成,并探讨了Dict的初始化过程。文章引用了一篇深入介绍Redis键值存储实现的文章作为参考。

Hash Table的数据结构从下到上可划分为:

// 存储键值对的地址
typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

// 底层哈希表的定义table是桶(bucket)。sizemask用于映射哈希值到桶中。sizemask = size - 1
typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

// 高层哈希表的定义,保存两个dictht的目的是为了平滑过度。
typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;

// 记录了dict的一些属性,如哈希函数,键值的赋值、析构函数等
typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

Dict初始化

对于第一次使用的dict是,redis会对dict的ht[0]进行初始化,为其table分配一个默认大小的空间:


int dictExpand(dict *d, unsigned long size)
{
    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size);

    /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

参考

https://medium.com/@kousiknath/a-little-internal-on-redis-key-value-storage-implementation-fdf96bac7453

### Redis Hash 数据结构与字典的关系 Redis 的 `Hash` 数据结构和字典(Dictionary)之间存在密切的关系,但它们并不完全等同。以下是关于两者关系的详细解析: #### 1. **Redis Hash 的底层实现** Redis 的 `Hash` 数据结构在底层有两种实现方式: - 当保存的所有键值对字符串长度小于 64 字节,并且键值对数量小于 512 时,使用 `ziplist` 实现[^2]。 - 如果键值对的数量或大小超出上述限制,则切换为字典(`dict`)的方式进行存储[^2]。 因此,Redis 的 `Hash` 数据结构并不是始终直接等同于字典,而是根据具体场景动态选择合适的底层实现。 #### 2. **字典的数据结构** Redis 的字典是基于哈希表实现的,其核心数据结构定义如下: ```c typedef struct dictht { dictEntry **table; // 存储键值对的哈希表 unsigned long size; // 当前哈希表的大小 unsigned long sizemask; // 计算哈希值的掩码值 unsigned long used; // 当前哈希表的节点数 } dictht; typedef struct dictEntry { void *key; // 指向键的指针 union { void *val; // 值的指针 uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; // 指向下一个 dictEntry 的指针 } dictEntry; ``` 通过上述定义可以看出,字典的核心是一个哈希表,能够提供高效的查找、插入和删除操作,平均时间复杂度为 O(1)[^3]。 #### 3. **Redis Hash 与字典的关系** 当 Redis 的 `Hash` 数据结构切换到字典实现时,它实际上就是一个标准的字典。此时,`Hash` 中的每个字段(field)作为字典的键(key),对应的值(value)作为字典的值(value)。例如,对于以下 Redis `Hash`: ```bash HSET myhash field1 "value1" HSET myhash field2 "value2" ``` 在字典实现中,会形成如下结构: ```plaintext { "field1": "value1", "field2": "value2" } ``` #### 4. **性能优化** 在 Redis 6 中,`dictFind` 函数的实现Redis 5 相同,但在查找过程中引入了 `dictEntryKey` 结构体中的哈希值进行比较,从而进一步提高了查找效率[^1]。这种优化使得字典的查找速度更快,进而提升了 `Hash` 数据结构的整体性能。 #### 5. **渐进式遍历** 为了应对大规模 `Hash` 数据结构可能带来的阻塞问题,Redis 提供了 `HSCAN` 命令。该命令以渐进式的方式遍历 `Hash` 中的所有字段和值,避免了一次性遍历导致的时间过长问题[^5]。这与 Java 中的 `ConcurrentHashMap` 扩容机制类似,分批处理数据以减少对系统的影响。 --- ### 示例代码 以下是一个简单的 Python 示例,展示如何通过 Redis 的 `Hash` 数据结构存储和查询信息: ```python import redis # 连接到 Redis r = redis.Redis(host='localhost', port=6379, decode_responses=True) # 设置 Hash 数据 r.hset("user:1000", mapping={"name": "Alice", "age": "25", "city": "New York"}) # 查询 Hash 数据 print(r.hgetall("user:1000")) # 输出: {'name': 'Alice', 'age': '25', 'city': 'New York'} # 渐进式遍历 Hash cursor, data = r.hscan("user:1000") while cursor != 0: cursor, new_data = r.hscan("user:1000", cursor=cursor) data.update(new_data) print(data) # 输出: {'name': 'Alice', 'age': '25', 'city': 'New York'} ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值