Redis设计与实现 笔记 第四章 字典

字典

字典是 Redis 中相当重要的结构,可以说, Redis 数据库的底层就是用字典实现的,而对数据库的增删改查,也是基于对字典的操作实现的.
字典的基本结构如下

4.1.1 哈希表
/* This is our hash table structure. Every dictionary has two of this as we
 * implement incremental rehashing, for the old to the new table. */
/*
 * 哈希表
 *
 * 每个字典都使用两个哈希表,从而实现渐进式 rehash 。
 */
typedef struct dictht {
    
    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;
    
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

} dictht;

分别记录当前哈希表中总数,已记录数,指针数组,构成基本结构,用哈希算法进行索引计算存放功能

4.1.2 哈希节点
/*
 * 哈希表节点
 */
typedef struct dictEntry {
    
    // 键
    void *key;

    // 值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
    } v;

    // 指向下个哈希表节点,形成链表
    struct dictEntry *next;

} dictEntry;

类似于 std::pair 的结构设计,拥有 key 和 val 成员变量,额外多出了 next 指针,内容是指向相同 hash 结果的下一个对象指针,也表明了 Redis 的 hash 碰撞用链表来解决,(另一种是拉链式,顺位+1),需要说的是,当相同 hash 结果得出后需要链表插入时,是插入至头部而非尾部,其复杂度从 O(N) 降至 O(1)

4.1.3 字典
/*
 * 字典类型特定函数
 */
typedef struct dictType {

    // 计算哈希值的函数
    unsigned int (*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;

dictType 这个结构用于存放特殊类型键值队函数接口调用存放,结构内全部是函数指针

/*
 * 字典
 */
typedef struct dict {

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */

} dict;

核心 dict 结构, 可以看到有两张 dictht 哈希表,正常情况下,只有一张表来进行数据的处理存储,另一张表将在处理 rehash 时进行使用.
rehashidx 表示当前 rehash 执行到哪个步骤

4.2 哈希算法

哈希算法指的是相同的输入会输出一个相同的结果的一个计算函数,评判算法的优劣可以通过两个方面,一个是算法的速度,一个算法算出的结果的一个均匀程度.
从 Redis 来讨论 .
可以认为算法结果越均匀,负载因子就越能表现当前字典中的负载程度,也就能减少 rehash 的发生…
Redis 使用的 hash 算法为 MurmurHash2.

4.3 解决键冲突

当哈希结果冲突时, dictEntry 使用链表的形式放置在第一个指针的位置

4.4. rehash

观察 dictht 的数据结构,在最坏的情况下,也就是所有的输入都输出了相同的 hash 结果时, table 将退化成一张链表结构, Redis 作为高校的数据库肯定是不容许该种情况,当然,正常工作环境中不会出现这种极端情况,但并不妨碍得出大量的链表结构将减慢执行效果的这一结论,所以 Redis 的处理行为为是进行 rehash 操作.
rehash 简单来说就是通过扩容来重新排列元素在 dictht 中所处的位置,是元素更均匀的分布,来减少链表结构的产生,以加快 Redis 的整体运行效率

    /* 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. */
    // 一下两个条件之一为真时,对字典进行扩展
    // 1)字典已使用节点数和字典大小之间的比率接近 1:1
    //    并且 dict_can_resize 为真
    // 2)已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        // 新哈希表的大小至少是目前已使用节点数的两倍
        // T = O(N)
        return dictExpand(d, d->ht[0].used*2);
    }

rehash 的条件较书中略有不同,但不妨碍我们对何时进行 rehash 这个行为的理解.
一下两个条件之一为真时,对字典进行扩展
1)字典已使用节点数和字典大小之间的比率接近 1:1并且 dict_can_resize 为真
2)已使用节点数和字典大小之间的比率超过 dict_force_resize_ratio

4.5 渐进式rehash

rehash 时我们并没有讨论具体场景, 我们在进行 rehash 的时候往往是认为一次性进行所有元素的重新计算, 其实这是不现实的,当我们元素只有几百个几千个时,我们当然可以,但一旦数量超过几个数量级时,达到几亿,几十亿时,我们还可以一口气进行 rehash 吗? 想象一下,如果真的决定一口气进行rehash,那么数据库一定会发生一段时间的无法响应,这显然是不允许的,所以,我们需要进行渐进式rehash.
但是随着 渐进式rehash 这个策略的确定,随之而来的问题会更多,比如,在 rehahs 过程中,如何正确的相应查询,插入,删除等命令就是一件不容易的事,因为此时数据已经发生了迁移.

rehash 的详细步骤
1): 为ht[1]分配空间,让字典同事持有ht[0] 和 ht[1].
2): 将 rehashidx 置为0, 表示开始进行 rehash.
3): 在 rehash 期间,发生查询,删除,更新,添加等行为时,首先去ht[0] 中尝试, 再去 ht[1] 尝试,而添加则直接去 ht[1] 中进行.
4): 在未来某个时间节点上,所有的数据迁移完毕,将 rehashidx 置为 -1, 表示 rehash 完成. ht[1] 设为 ht[0] ,移除 ht[0]

总结

字典用哈希表来进行实现,每个字段有持有两张哈希表,用来伸缩当前的表格达到加快效率的目的.
哈希表通过链表来解决哈希冲突,当负载因子满足条件时,会进行 rehash 操作.
rehash 行为是渐进式的, 会逐步进行数据迁移.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值