首页 > 编程知识 正文

redis字典结构,hash冲突怎么办,rehash,负载因子,redishash碰撞

时间:2023-05-06 06:22:09 阅读:186044 作者:3512

文章目录1. hash算法2. rehash3.哈希表的扩展和收缩4 .解决散列冲突5 .渐进rehash

1 .混列算法

学习了数据结构,在将新的键值对添加到字典中时,程序首先根据键值对的键计算哈希值和索引值,然后基于该索引值,将包含新键值对的散列表节点指定为散列表数组

哈希值和索引值的计算方法如下。 #使用字典中设置的散列函数计算密钥key的散列值hash=dict-type-hash function (key )。 #使用散列表中的sizemask属性和哈希值计算索引值#在某些情况下,ht[x]可能是ht[0]或ht [1] index=hash dict-ht [ x ].size mask 例如,如果要将键值对(k0,v0)添加到词典中,程序会首先使用语句。

hash=dict-type-hashfunction(k0; 计算密钥k0的哈希值。

假设计算出的哈希值为8,程序将继续使用语句。

index=hash dict-ht [0].size mask=83=0; 计算密钥k0的索引值0。 这意味着包含键值对k0和v0的节点将位于哈希表数组的索引0处,如下图所示

那么具体用什么算法来计算索引值呢?

当字典用作数据库的基本实现或lkdfs的基本实现时,Redis使用MurmurHash2算法计算密钥的哈希值。

2008年由Austin Appleby首先发明的MurmurHash算法的优点是,即使输入的密钥是规则的,算法也具有很好的随机性,算法的计算速度也非常快。

2 .随着操作的进行,2. rehash会逐渐增加或减少保存在散列表中的键值对。 如果为了使散列表的负载率(load factor )【保存在散列表中的节点数/散列表的大小】维持在适当的范围内,保存在散列表中的键值对数量过多或过少,程序就会失败

哈希表的扩展和收缩是通过rehas重新哈希操作完成的,如下所示。

为词典中的ht[1]哈希表分配空格。 此散列表的空间大小取决于要执行的操作以及ht[0]中当前包含的键值对的数量,即ht[0].used属性的值。

执行扩展操作时,以ht[1]的大小为首,ht[0].used * 2以上的数值数。 并且,这个数是2的n次方。 例如,如果ht[0].used=7,则ht[0].used * 2=14,最初为14以上的数,且为2的n次方,该数为16=2^4,因此ht[1]的大小为16 进行收缩操作时,以ht[1]的大小为首,ht[0].used以上的2^n。 将保存在ht[0]中的所有键值对从rehash放置在ht[1]上。 rehash是重新计算密钥的哈希值和索引值,并将密钥值对放置在ht[1]哈希表中的指定位置。

ht[0]中包含的所有键值对都迁移到ht[1]后,ht[0]将成为空表。 释放ht[0],将ht[1]设定为ht[0],在ht[1]中创建新的空哈希表,为下一个rehash做准备。

书中的例子

假设程序对上图词典中的ht[0]执行扩展操作,该程序将执行以下步骤:

由于ht[0].used的当前值为4,4 *2=8,8 (2^3)正好是前4以上2的n次方,因此程序将ht[1]哈希表的大小设置为8。

将ht[0]中包含的所有四个键值对重置为ht[1]。

释放ht[0],将ht[1]设置为ht[0],然后为ht[1]分配空白散列表。

现在,您已经完成了对哈希表的扩展操作,程序成功地将哈希表的大小从原来的4更改为当前的8。

那么什么时候进行rehash?

3 .哈希表扩展和收缩当满足以下条件之一时,程序自动开始哈希表扩展操作:

服务器当前没有运行BGSAVE或BGREWRITEAOF命令,散列表的负载率大于或等于1。 服务器当前正在运行BGSAVE或BGREWRITEAOF命令,散列表的负载率至少为5

这里,散列表负载因子为#负载因子=已保存散列表的节点数/散列表大小load _ factor=ht [0].used/ht [0].size,例如,大小为4,密钥

在大小为512、具有256个键值对散列表中,负荷系数为load_

factor = 256 / 512 = 0.5

当哈希表的负载因子小于 0.1 时, 程序自动开始对哈希表执行收缩操作。

4. 解决哈希冲突

Redis 的哈希表使用链地址法(separate chaining)来解决键冲突: 每个哈希表节点都有一个 next 指针, 多个哈希表节点可以用 next 指针构成一个单向链表, 被分配到同一个索引上的多个节点可以用这个单向链表连接起来, 这就解决了键冲突的问题。

因为 dictEntry 节点组成的链表没有指向链表表尾的指针, 所以为了速度考虑, 程序总是将新节点添加到链表的表头位置(复杂度为 O(1)), 排在其他已有节点的前面。

如下图:

5. 渐进式rehash

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

这样做的原因在于, 如果 ht[0] 里只保存着四个键值对, 那么服务器可以在瞬间就将这些键值对全部 rehash 到 ht[1] ; 但是, 如果哈希表里保存的键值对数量不是四个, 而是四百万、四千万甚至四亿个键值对, 那么要一次性将这些键值对全部 rehash 到 ht[1] 的话, 庞大的计算量可能会导致服务器在一段时间内停止服务。

因此, 为了避免 rehash 对服务器性能造成影响, 服务器不是一次性将 ht[0] 里面的所有键值对全部 rehash 到 ht[1] , 而是分多次、渐进式地将 ht[0] 里面的键值对慢慢地 rehash 到 ht[1] 。

以下是哈希表渐进式 rehash 的详细步骤:

为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。

渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均摊到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。

因为在进行渐进式 rehash 的过程中, 字典会同时使用 ht[0] 和 ht[1] 两个哈希表, 所以在渐进式 rehash 进行期间, 字典的删除(delete)、查找(find)、更新(update)等操作会在两个哈希表上进行: 比如说, 要在字典里面查找一个键的话, 程序会先在 ht[0] 里面进行查找, 如果没找到的话, 就会继续到 ht[1] 里面进行查找, 诸如此类。

另外, 在渐进式 rehash 执行期间, 新添加到字典的键值对一律会被保存到 ht[1] 里面, 而 ht[0] 则不再进行任何添加操作: 这一措施保证了 ht[0] 包含的键值对数量会只减不增, 并随着 rehash 操作的执行而最终变成空表。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。