目录
Dict结构设计 — rehash
rehash触发机制
Dict扩容
Dict收缩
编辑渐进式 rehash
哈希表优点在于,它能以 O(1) 的复杂度快速查询数据。为解决哈希冲突,Redis 采用了「链式哈希」来解决哈希冲突,在不扩容哈希表的前提下,将具有相同哈希值的数据串起来,形成链接起,以便这些数据在表中仍然可以被查询到。
Dict结构设计 — rehash
在实际使用哈希表时,Redis 定义一个 dict 结构体里包含了两个哈希表(ht[2]),即rehash。Dict由三部分组成,分别是:哈希表(DictHashTable)、哈希节点(DictEntry)、字典(Dict)
问:rehash为什么用两个Dict结构
答:当触发rehash条件时,自动进行扩容或收缩
rehash触发机制
Dict在每次新增键值对时都会检查负载因子,对应于源码为【used / size】:
满足以下两种情况时会触发哈希表扩容:
- 当负载因子大于等于 1 ,并且 Redis 没有在执行 bgsave 命令或者 bgrewiteaof 命令,也就是没有执行 RDB 快照或没有进行 AOF 重写的时候,就会进行 rehash 操作。
- 当负载因子大于等于 5 时,此时说明哈希冲突非常严重了,不管有没有有在执行 RDB 快照或 AOF 重写,都会强制进行 rehash 操作。
满足以下情况时会触发哈希表收缩:
- 每次删除成功时,也会对负载因子做检查,当LoadFactor < 0.1 时,会做哈希收缩
Dict扩容
在正常服务请求阶段,当向Dict添加键值对时,Redis首先根据Key计算出hash值,然后利用hash值 & sizemask做与运算确定插入的索引位置(下标)<也是因为这个与运算,使得size必须是2的n次方>,都会写入到【哈希表 1】,此时的【哈希表 2 】并没有被分配空间。当触发rehash扩容条件时,触发rehash扩容,这个过程分为四步:
- 给【哈希表 2 】分配空间,一般会比【哈希表 1】大 2 倍;
- 将【哈希表 1】的数据重新做映射计算哈希值求下标,然后迁移到【哈希表 2 】中;
- 迁移完成后,【哈希表 1】的空间会被释放,并把【哈希表 2 】赋值给【哈希表 1】
- 然后给【哈希表 2 】初始化为一个空白的哈希表,为下次 rehash 做准备。
扩容源码片段:
Dict收缩
每次删除数据成功时,判断是否负载因子小于0.1,进行哈希表收缩。
渐进式 rehash
情景:如果【哈希表 1】的数据量非常大,那么在迁移至【哈希表 2 】的时候,因为会涉及大量的数据拷贝,此时可能会对 Redis 造成阻塞,无法服务其他请求。
为了避免 rehash 在数据迁移过程中,因拷贝数据的耗时,影响 Redis 性能的情况,所以 Redis 采用了渐进式 rehash,也就是将数据的迁移的工作不再是一次性迁移完成,而是分多次迁移。
渐进式 rehash 步骤如下:
- 给【哈希表 2 】 分配空间;
若是扩容,则新size为第一个大于等于(【哈希表 1】的used+1)的2的n次方<2的n次方与计算哈希表下标有关,上文有提>
若是收缩,则新size为第一个大于等于(【哈希表 1】的used)的2的n次方<不能小于4>- 在渐进式 rehash 进行期间,每次哈希表元素进行新增、删除、查找或者更新操作时,Redis 除了会执行对应的操作之外,还会顺序将「哈希表 1 」中索引位置上的所有 key-value 迁移到「哈希表 2」 上;
- 随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间点会把「哈希表 1 」的所有 key-value 迁移到「哈希表 2」,从而完成 rehash 操作
在渐进式 rehash的过程中,增删改查执行机制:
- 查找、删除、修改一个 key 的值的话,先会在「哈希表 1」 里面进行操作,如果没找到,就会继续到哈希表 2 里面进行操作。
- 新增一个 key-value 时,会被保存到「哈希表 2 」里面,而「哈希表 1」 则不再进行任何添加操作