Redis - 字典的实现与哈希冲突解决
1. 字典的实现
redis的字典数据类型的实现主要分为两个部分:
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
long rehashidx;
unsigned long iterators;
} dict;
其中,type属性表示字典的类型,而privdata属性表示字典的私有数据,它是一个指针类型,可以指向一个可选的私有数据结构。
值得注意的是,Redis中的字典使用了两个哈希表(dictht ht[2]),一个用于平时的读写操作,另一个仅在rehash过程中使用,用作新的哈希表。
2. 哈希算法
在Redis中,主要采用MurmurHash2这个非加密哈希算法。此算法的优点主要有三个:
- 速度快:MurmurHash算法的速度非常快,可以在很短的时间内计算出哈希值。
- 均匀性好:MurmurHash算法的哈希值分布均匀,因此冲突的概率较低。
- 易于实现:MurmurHash算法的实现非常简单。
3. 解决键冲突
当两个或两个以上的键被分配到哈希数组的同一个索引上面时,称这些键发生了哈希冲突。
解决哈希冲突,Redis采用了“链地址法”:当两个键的哈希值相同时,会将它们链接在同一个哈希槽(slot)上,形成一个链表。
4. rehash
1)负载因子
负载因子 = used / size ; used 是哈希数组存储的元素个数,size 是哈希数组的长度。
负载因子越小,冲突越小;负载因子越大,冲突越大。
2)rehash
随着命令的不断执行,哈希表保存的减值对会逐渐增加或者减少,为了让哈希表的负载因子维持在一个合理的范围内,当哈希表中的键值对过多或过少时,需要对哈希表的大小进行相应的扩展和收缩。而哈希表的扩展和收缩可以通过rehash来执行。rehash 就是将 ht[0] 中的节点,通过重新计算哈希值和索引值放到 ht[1] 哈希表指定的位置上。
扩容:
如果负载因子大于1,就会触发扩容,扩容的规则是每次翻倍;
如果正在fork,执行持久化则不会扩容,但是,如果负载因子大于5,会立马扩容。
缩容:
如果负载因子小于0.1,就会触发缩容。缩容的规则是:恰好包含used的2^n。
3)渐进式rehash
当哈希表中的元素过多时,如果一次性rehash到ht[1],庞大的计算量,可能导致redis服务在一段时间不可用。为了避免rehash对服务器带来的影响,redis分多次、慢慢的将ht[0]哈希表中的键值对rehash到ht[1]哈希表,这就是渐进式rehash。
核心思想:将整个rehash过程均摊到每次命令的执行中。
4)rehash的详细步骤:
为 ht[1] 哈希表分配空间,此时字典同时拥有ht[0] 和 ht[1] 两个字典
将字典中的rehashidx设置为0,表示开始rehash
在rehash期间,每次对字典的增删改查,除了执行指定的命令外,还会顺带将ht[0] 中 rehashidx 索引上的所有键值对都rehash到ht[1]中,执行完rehash,rehashidx属性加一。注意:新增的键值对只能插入到ht[1]哈希表中,保证ht[0]的键值对只减不增。
随着操作的不断进行,最终ht[0]哈希表中的所有键值对都被rehash到ht[1]中。此时,将ht[0]释放掉,让ht[0] 指向ht[1],并设置rehashidx 为 -1,表示rehash完成。
注意:删除、修改和查找可能会在两个散列表进行,第一个散列表没找到就到第二个散列表【ht[1]】进行查找,但增加操作只会在新的散列表上进行。
5)rehash带来的问题
渐进式rehash避免了redis阻塞,可以说非常完美,但是由于在rehash时,需要分配一个新的hash表,在rehash期间,同时有两个hash表在使用,会使得redis内存使用量瞬间突增,如果当前Redis结点的内存占用量达到maxmemory(Redis满容状态), 会触发内存淘汰机制,导致大量的Key被驱逐。
5. 哈希表的扩容与收缩
总结
Redis字典的实现充分考虑了性能和内存效率,采用了一系列优化策略来保证其高效的运行,包括使用了MurmurHash算法,采用链地址法处理哈希冲突,以及通过渐进式rehash避免操作的长时间阻塞等。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」