Redis字典扩容

字典结构

redis字典结构是整个数据库的底层实现,也是hash类型对象和set类型对象的底层实现之一,是一种用于保存键值对的数据结构,redis中一个未处于扩容状态且含有两个键值对的完整的字典结构图如下:

dict即字典结构,它包含:

  1. type:一个指向dictType结构的指针,dictType记录了一组操作特定类型键值对的函数;
  2. privdata:记录字典私有数据,需要作为参数传递给特定函数;
  3. ht:指向两个哈希表,通常情况下只使用ht[0]一个哈希表,ht[1]是为扩容/收缩时准备的;
  4. rehashidx:记录渐进式rehash的进度,若没有进行rehash,值为-1。

dictht是哈希表,它包含:

  1. table:指向一个键值对数组,使用链地址法解决hash冲突;
  2. size:哈希表大小,也就是当前的数组长度;
  3. sizemask:哈希表大小掩码,永远等于size-1,用于计算键值对索引,添加一个新的键值对时它的索引等于hash(key)&sizemask;
  4. used:该哈希表已有键值对数量。

集中式rehash

在介绍渐进式rehash之前,先来介绍一下集中式rehash,从目的和结果出发来看这两种rehash方式都是一样的。

rehash时机
负载因子=哈希表已有键值对数量 / 哈希表大小 = used / size;

扩容:
1)如果Redis当前没有执行BGSAVE或BGREWRITEAOF命令,会在负载因子大于等于1时rehash;
2)如果Redis当前正在执行BGSAVE或BGREWRITEAOF命令,会在负载因子大于等于5时rehash。
收缩:
负载因子小于0.1时进行rehash。

之所以依据是否在执行BGSAVE或BGREWRITEAOF命令,rehash的门限会不同,在《Redis设计与实现》中是这样解释的:

这是因为在执行BGSAVE或BGREWRITEAOF命令的过程中,Redis需要创建当前服务器进程的子进程,而大多数操作系统都采用写时复制技术来优化子进程的使用效率,所以在子进程存在期间,服务器会提高执行扩展操作所需的负载因子,从而尽可能避免在子进程存在期间对哈希表进行扩展操作,这可以避免不必要的内存写入操作,尽最大限度地节约内存。

Redis在执行BGSAVE或BGREWRITEAOF命令时,会fork出一个子进程来生成rdb文件或重写AOF文件,采用写时复制技术,父子进程共享同一片内存区域,当任一进程企图对这片内存区域进行写入操作时,会把将要写入的那一部分内存页复制一份进行写入,其他内存页依旧共享。在字典扩容期间,父进程要迁移数据,不可避免的会有大量内存写入操作,为了减少内存页过多的复制,而提高了扩容的门限,这是出于节省内存考虑的。

至于为什么Redis字典收缩时不用考虑写时复制?我认为原因有两个:一是收缩条件是键值对个数小于哈希表长度的10%,有意设置的这么低,就是为了不会造成过多的页分离;二是收缩操作完成会释放一部分内存,本身目的就是节省内存的。所以两个原因综合起来,不用考虑写时复制对收缩的影响。

rehash过程
①首先在ht[1]的位置初始化一个键值对数组,数组长度取决于执行扩容还是收缩操作:

  • 扩容:键值对数组长度为第一个大于等于ht[0].used*2的2^n
  • 收缩:键值对数组长度为第一个大于等于ht[0].used的2^n


②将ht[0]的元素迁移到ht[1]上,重新计算hash值和索引;

③释放ht[0],将ht[1]设置为ht[0],在ht[1]的位置新建一个空的哈希表,以备下次rehash。

渐进式rehash

rehash的过程可能会行当耗时,所以Redis采用的是分多次地、渐进式地rehash,即渐进式rehash。

渐进式rehash过程
①为ht[1]初始化一个新数组,并将dict字典结构的rehashidx属性的值由-1设为0,标志rehash开始;
②在rehash期间,每一次接收到对字典的增删改查命令时,除了执行相应操作,还会把ht[0]上索引为rehashidx位置的所有元素(整个链表)rehash到ht[1]的相应索引上,每执行一次上述操作,rehashidx的值就加一;
③ht[0]所有键值对都被rehash到ht[1]上之后,释放ht[0],将ht[1]设置为ht[0],在ht[1]的位置新建一个空的哈希表,以备下次rehash。

其他注意事项

  1. rehash过程中会使用两个哈希表执行查找、更新、删除命令,如先在ht[0]中查找元素,没有找到再去ht[1]中找,以此类推;
  2. rehash过程中的所有添加命令都在ht[1]上执行;
  3. 如果一段时间内Redis没有接收到命令触发渐进式rehash,Redis会在定时任务中对字典进行rehash。

参考资料

《Redis设计与实现》

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值