redis数据结构 字典 哈希表 用于hash键底层实现 总结

Redis中的字典通过哈希表实现,使用链地址法解决冲突,当负载因子超过阈值时进行渐进式rehash。rehash过程中,字典同时使用两个哈希表,新插入的键值对直接添加到新表,逐步迁移旧表数据,以平衡哈希表负载并确保操作效率。
摘要由CSDN通过智能技术生成

1.原理
字典是哈希键的底层实现,当一个哈希键包含的键值对比较多,或者键值对中元素都是比较长的字符串,Reids就会使用字典作为哈希键的底层实现

2.字典节点结构

typedef struct dictEntry {
void *key //键
union {
      void *val;
      unit64_t u64;
      int64_t s64;
}v;//值
struct dictEntry *next; // 指向下一个哈希表节点 形成链表
}dictEntry;

字典节点也是链表结构,用于哈希冲突后形成链表

3.字典结构

typedef struct dict {
dictType *type //类型特定函数
void *privdata是// 私有数据
dictht ht[2]; //哈希表
int trehashidx; // rehash索引 当rehash不在进行时 值位-1
}dict;

type属性和privdata属性针对不同类型的键值对,为创建多态字典而设置的
dictType结构里面封装了各自方法
ht[2]是一个包含两个哈希表的数组,一般情况,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时候使用,即在对字典扩容的时候使用

4.dictType结构

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 (*valDeestructor) (void *privdata, void *bje) //销毁值的函数
}dictType;

5.哈希算法
添加一个新的键值对到字典里面时,根据键值对计算出哈希值和索引值,然后根据索引值,把包含新键值对的哈希表节点放到哈希表数组指定索引上

hash = dict->type->hashFunction(key) 使用字典设置的哈希函数,计算键key的哈希值,然后使用哈希表的sizemask属性和哈希值,计算出索引值
index = hash & dict->ht[x]->sizemask;

6.解决键冲突
Redis的哈希表使用链地址法来解决键冲突,因为dictEntry节点组成的链表没有指向链表表尾的指针,为了速度考虑,总是将新节点添加到链表的表头位置

7.rehash
随着操作的不断执行,哈希表保存的键值对会逐渐地增多或减少,为了让哈希表的负载因子维持维持在一个合理的范围之内,当哈希表保存的键值对数量太多或太少,程序需要对哈希表的大小进行相应的扩展或收缩

8.扩容和缩容操作
扩展和收缩哈希表,可以通过执行rehash作来完成

  1. 为字典的ht[1]哈希表分配空间,大小取决于要执行的操作,以及ht[0]当前包含的键值对数量
    扩展操作 设置ht[1]大小为第一个2倍ht[0]已使用空间大的2的次幂
    收缩操作 设置ht[1]大小为第一个二分之一ht[0]已使用空间小的2的次幂
  2. 将保存在ht[0]中是所有键值对rehash到ht[1]上面,rehash指的是重新计算键的哈希值和索引值,将键值对放置到,ht[1]哈希表指定位置上
  3. 当ht[0]包含的所有键值对都迁移到了ht[1]之后 ht[0]变为空表,释放ht[0] 将ht[1]设置为ht[0],并在ht[1]新创建一个空白hash表,为下一次rehash做准备

8.扩容和缩容操作时机
负载因子 = 哈希表已经保存节点数量 / 哈希表大小

  1. 服务器目前没有执行BGSAVE命令或BGREWRITEAOF, 并且负载因子大于1
  2. 服务器目前正在执行BGSAVE命令或者BGREWRITEAOF,并且负载因子大于5
  3. 当负载因子小于0.1时程序自动开始对哈希表执行收缩操作

9.渐进式rehash
扩展或收缩哈希表需要将ht[0]里面的所有键值对rehash到ht[1],但是这个rehash动作并不是一次、集中式地完成的,而是分多次、渐进式完成的

10.渐进式rehash步骤

  1. 为ht[1]分配空间在字典中维持一个索引计数器变量 rehashidx 并将它的值设置为0,表示rehash工作正式开始
  2. 进行rehash期间,每次对字典执行添加、删除、查找或者更新操作,程序除了执行指定的操作,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1]中,当rehash工作完成后,程序将rehashidx属性的值增一,随着字典操作不断执行,最终在某个时间点 ht[0]的所有键值对都被rehash至ht[1],这时程序将rehashidx属性设置为-1,表示rehash操作已经完成,渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对所需的计算工作均摊到字典的每个添加、删除、查找和更新,操作上,从而避免了集中式rehash而带来的庞大计算量

11.渐进式rehash执行期间的哈希表操作
在渐进式rehash操作中,字典会同时使用ht[0]和ht[1]两个哈希表,所以在渐进式rehash进行期间,字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行。例如在字典上查找一个键如果在ht[0]没有找到,就去ht[1]找在渐进式rehash执行期间,新添加到字典的键值对一律会保存到ht[1]里面而ht[0]不执行任何添加操作,这一措施保证了ht[0]包含的键值对数量会只减少不增加,并随着rehash操作的执行而最终变成了空表

12.总结
1.字典被广泛用于实现Redis的各种功能,包括数据库和哈希表
2.Redis中的字典使用哈希表作为底层实现,每个字典带有两个哈希表,一个平时使用,一个仅在进行rehash使用
3.当字典被用作数据库的底层实现,或者哈希键的底层实现,Reids使用 MurmurHash2算法来计算键的哈希值
4.哈希表使用链地址法来解决键冲突,被分配到同一个索引上的多个键值对会连接成一个单向链表
5.在对哈希表进行扩展或收缩操作时,程序需要将现有哈希表包含的所有键值对对rehash到新哈希表里面,并且这个rehash过程并不是一次性地完成的,而是渐进式地完成的

参考
《Redis设计与实现》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lolxxs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值