Redis第十讲 Redis之Hash数据结构Dict-rehash扩容操作

Redis在哈希表扩容时采用增量式Rehash策略,避免一次性迁移所有键值对,将rehash分散到多个操作中进行,以减少响应时间并平滑内存占用。在rehash过程中,新添加的元素直接进入新哈希表,查找、删除等操作同时在两个表上执行,确保数据一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Rehash 执行过程

字典的 rehash 操作实际上就是执行以下任务:

  • 创建一个比 ht[0]->table 更大的 ht[1]->table ;
  • 将 ht[0]->table 中的所有键值对迁移到 ht[1]->table ;
  • 将原有 ht[0] 的数据清空,并将 ht[1] 替换为新的 ht[0] ;
    经过以上步骤之后, 程序就在不改变原有键值对数据的基础上, 增大了哈希表的大小。

dict的rehash 本质就是扩容,就是将数组+链表结构中的数组扩容;
这个过程,需要开辟一个更大空间的数组,将老数组中每个非空索引的bucket,搬运到新数组;搬运完成后再释放老数组的空间。

作为例子, 以下四个小节展示了一次对哈希表进行 rehash 的完整过程。
1: 开始 rehash
这个阶段有两个事情要做:

  • 设置字典的 rehashidx 为 0 ,标识着 rehash 的开始;
  • 为 ht[1]->table 分配空间,大小至少为 ht[0]->used 的两倍;

这时的字典是这个样子:

在这里插入图片描述
2: Rehash 进行中
在这个阶段, ht[0]->table 的节点会被逐渐迁移到 ht[1]->table , 因为 rehash 是分多次进行的(细节在下一节解释), 字典的 rehashidx 变量会记录 rehash 进行到 ht[0] 的哪个索引位置上。

注意除了节点的移动外, 字典的 rehashidx 、 ht[0]->used 和 ht[1]->used 三个属性也产生了变化。

3: 节点迁移完

到了这个阶段,所有的节点都已经从 ht[0] 迁移到 ht[1] 了:

在这里插入图片描述
4: Rehash 完毕
在 rehash 的最后阶段,程序会执行以下工作:

  • 释放 ht[0] 的空间;
  • 用 ht[1] 来代替 ht[0] ,使原来的 ht[1] 成为新的 ht[0] ;
  • 创建一个新的空哈希表,并将它设置为 ht[1] ;
  • 将字典的 rehashidx 属性设置为 -1 ,标识 rehash 已停止;

以下是字典 rehash 完毕之后的样子:
在这里插入图片描述

incremental rehashing 增量/渐进式rehash

在前面我们已经了解了字典的 rehash 过程, 需要特别指出的是,rehash 并不是在触发之后,马上就执行直到完成, 而是分多次、渐进式地完成的

rehash会产生的问题

  • rehash的过程,会使用两个哈希表,创建了一个更大空间的ht[1],此时会造成内存陡增;
  • rehash的过程,可能涉及大量KV键值对dictEntry的搬运,耗时较长;

如果这个 rehash 过程必须将所有键值对迁移完毕之后才将结果返回给用户, 这样的处理方式将不满足Redis高效响应的特性。

rehash会产生的问题

  • 主要层面就是内存占用陡增、和处理耗时长的问题
  • 基于这两点,还会带来其他影响。

为了解决这些问题, Redis 使用了incremental rehashing,是一种 增量/渐进式的 rehash 方式: 通过将 rehash 分散到多个步骤中进行, 从而避免了集中式的计算/节点迁移。

dictAdd 添加键值对到dict,检查到需要进行rehash时,会将dict.rehashidx 设置为 0 ,标识着 rehash 的开始;

后续请求,在执行add、delete、find操作时,都会判断dict是否正在rehash,如果是,就执行_dictRehashStep()函数,进行增量rehash。

每次执行 _dictRehashStep , 会将ht[0]->table 哈希表第一个不为空的索引上的所有节点就会全部迁移到 ht[1]->table 。

也就是在某次dictAdd 添加键值对时,触发了rehash;后续add、delete、find命令在执行前都会检查

  • 如果dict正在rehash,就先不急去执行自己的命令,先去帮忙搬运一个bucket;
  • 搬运完一个bucket,再执行add、delete、find命令 原有处理逻辑。
    在这里插入图片描述

实际上incremental rehashing增量/渐进式rehash,只解决了第二个:耗时长的问题,将集中式的节点迁移分摊到多步进行,ht[1]占用的双倍多内存,还一直占用。

下面我们通过dict的查找(dictFind)来看渐进式rehash过程;

dict的查找(dictFind)

dictEntry *dictFind(dict *d, const void *key)
{
   
   
    dictEntry *he;
    unsigned int h, idx, table;

    if (d->ht[0].used + d->ht[1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员路同学

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

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

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

打赏作者

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

抵扣说明:

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

余额充值