Redis---渐进式哈希

7 篇文章 0 订阅
2 篇文章 0 订阅

Redis支持的数据结构有很多,其中dict的使用非常频繁,其实Redis的每一个数据库结构都是一个dict。dict使用哈希表实现,这也是Redis性能十分强悍的原因之一,增删改查的时间复杂度为O(1).

在这里插入图片描述

上图是我根据Redis源码中定义的数据结构及网上资料参考画的参考图。

随着Redis的操作越来越多,dict中保存的数据量也会动态变化,当数据量增加或者减少到一定的程度,为了让负载因子维持在一个合理的范围内,Redis就会对dict的大小进行相应的扩容或者收缩。而这一过程正是通过渐进式哈希(rehash)操作来完成的。

渐进式哈希的原理:

将原哈希表中的数据以少量多次的方式,rehash到新的哈希表中,避免一次性数据迁移导致堵塞问题,通过rehashidx记录rehash的进度,在rehash结束后,新的哈希表将替代原哈希表。

在这里插入图片描述

在正式了解渐进式哈希之前,我们先来看几个重要的概念:

负载因子:ht[0].used / ht[0].size,即哈希表的填满程度。它决定了哈希表的元素多少、空间利用率高低、哈希冲突机会的大小以及操作开销程度等,本质上是数据结构中有名的“时-空”矛盾。
sizemask:也叫大小掩码,用来计算索引值,其值等于哈希表size - 1。
rehashidx:当rehash结束时,其值为-1,rehash进行时,其值为rehash的进度,以bucket为单位,一个bucket可能有多个元素。
iterators:当前正在运行的安全迭代器数量。
渐进式哈希的步骤:

  1. 判断负载因子,进行rehash操作。
  2. 申请ht[1]的内存空间,此时dict同时拥有ht[0]和ht[1]。
    扩容:ht[1]的大小为大于等于ht[0].used * 2的且为2^n的值。
    收缩:ht[1]的大小为大于等于ht[0].used 的且为2^n的值。
  3. 将dict.rehashidx置为0,开始对dict.ht[0].table[0]的bucket进行rehash。
    因为一个bucket是一个链表式结构,所以循环遍历这个bucket上的元素。
    计算每一个元素中key的新哈希值,与dict.ht[1].sizemask进行位与运算,得到索引h。
  4. 根据索引h,将元素插入ht[1].table[h]。
  5. 更新ht[0].used–,ht[1].used++。
  6. 继续处理bucket上的下一个元素。
  7. 处理完一个bucket后,将ht[0].table[dict.rehashidx] 置为 NULL。
  8. 将dict.rehashidx加1,处理下一个bucket:ht[0].table[dict.rehashidx]。
  9. 直到ht[0].used 为 0,说明ht[0]中的所有元素完成数据迁移。
    释放ht[0].table内存,将ht[1]赋值给ht[0],然后重置ht[1],为下一次rehash做准备。
  10. 将dict.rehashidx置为-1,rehash工作正式结束。

渐进式哈希的控制:

/d为需要rehash的字典,n为bucket的个数/

int dictRehash(dict *d, int n)

/在ms时间段内进行rehash操作/

int dictRehashMilliseconds(dict *d, int ms)

rehash操作默认是分步的,即一次只rehash一个bucket:n = 1。
如果bucket为null,则继续处理下一个,但是不能超过10n个为null的bucket:
按时间段rehash,在时间段内每次rehash 100个bucket:n = 100,直到超时。
连续的10
n个为null的bucket算为一个bucket。
渐进式哈希过程中访问元素:

rehash操作非一蹴而就,在rehash的过程中,ht[0]和ht[1]同时存放着数据,但是有dict.rehashidx变量标识着rehash的进度,即可以通过dict.rehashidx判断哈希值存在于ht[0]还是ht[1]。

如果是新增元素,会直接操作ht[1],保证ht[0]的数据只减不增。
如果dict.rehashidx值为-1,则当前没有rehash操作,直接操作ht[0]。
如果dict.rehashidx值大于等于0,则表示正在进行rehash操作。
将计算的ht[0]中的索引与dict.rehashidx比较,如果索引大于dict.rehashidx,表示索引还未rehash,直接操作ht[0]。
如果索引不大于dict.rehashidx,则表示索引已经rehash到ht[1]。
根据ht[1]重新计算索引,根据索引操作ht[1]。
结束。

总结:

渐进式哈希的设计无疑是优秀的,在动态扩容收缩空间的同时,保证了Redis的服务能力,避免了阻塞;但是在rehash期间,dict同时拥有ht[0]和ht[1],申请内存空间后内存会瞬间增长,此时可能会触发Redis的过期机制或者内存淘汰策略以释放更多的内存,尤其是Redis作为lru cache长期处于maxmemory状态,势必会删除大量的key

参考资料
https://baijiahao.baidu.com/s?id=1663870339713597715&wfr=spider&for=pc

关注公众号:关大仙的学习笔记

后续高质量文章都会发布到这个公众号上, 还有面试共享群可以添加

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis渐进式rehash是一种在进行哈希表扩容或收缩时的一种渐进式处理方式。它避免了Redis阻塞的问题,但也带来了一些其他的问题。在进行渐进式rehash时,Redis需要分配一个新的哈希表,并为该哈希表分配新的大小的内存。这导致了Redis内存使用量瞬间增加,并且在Redis满容状态下,rehash操作会导致大量的Key被驱逐。 为了解决这个问题,Redis实现了辅助服务器来在读写操作时进行渐进式rehash操作。但是如果服务器比较空闲,Redis数据库将会长时间同时使用两个哈希表。为了处理这种情况,Redis周期函数在发现字典正在进行渐进式rehash操作时,会花费1毫秒的时间来帮助进行渐进式rehash操作。 总之,Redis渐进式rehash是一种有效避免阻塞的哈希表扩容或收缩方式,但在操作过程中可能会导致内存使用量增加和大量Key被驱逐的问题。为了处理这些问题,Redis使用辅助服务器和周期函数来优化渐进式rehash操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis详解(六)渐进式rehash机制](https://blog.csdn.net/fedorafrog/article/details/104633237)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值