前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >redis之rehash原理

redis之rehash原理

作者头像
码农编程进阶笔记
发布2023-03-23 12:58:47
5230
发布2023-03-23 12:58:47
举报
文章被收录于专栏:码农编程进阶笔记

字典的结构

在 Redis 中所有的 key 都存储在一个很大的字典中,这个字典的结构和 Java 中的 HashMap 一样,是一维数组 + 二维链表结构,如下图,第一维数组的大小总是 2^n(n>=0) ,扩容一 次数组大小空间加倍,也就是 n++

scan 指令返回的游标就是第一维数组的位置索引,我们将这个位置索引称为槽 (slot) 。如果不考虑字典的扩容缩容,直接按数组下标挨个遍历就行了。limit 参数就表示需要遍历的槽位数,之所以返回的结果可能多可能少,是因为不是所有的槽位上都会挂接链表,有些槽位可能是空的,还有些槽位上挂接的链表上的元素可能会有多个。每一次遍历都会将 limit 数量的槽位上挂接的所有链表元素进行模式匹配过滤后,一次性返回给客户端

scan 遍历顺序

scan 的遍历顺序非常特别。它不是从第一维数组的第 0 位一直遍历到末尾,而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历,是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏

普通加法和高位进位加法的区别

高位进位法从左边加,进位往右边移动,同普通加法正好相反。但是最终它们都会遍历所有的槽位并且没有重复

字典扩容

Java 中的 HashMap 有扩容的概念,当 loadFactor 达到阈值时,需要重新分配一个新的2 倍大小的数组,然后将所有的元素全部 rehash 挂到新的数组下面。rehash 就是将元素的hash 值对数组长度进行取模运算,因为长度变了,所以每个元素挂接的槽位可能也发生了变 化。又因为数组的长度是 2^n 次方,所以取模运算等价于位与操作

a mod 8 = a & (8-1) = a & 7

a mod 16 = a & (16-1) = a & 15

a mod 32 = a & (32-1) = a & 31

这里的 7, 15, 31 称之为字典的 mask 值, mask 的作用就是保留 hash 值的低位,高位都被设置为0,接下来我们看看 rehash 前后元素槽位的变化

假设当前的字典的数组长度由 8 位扩容到 16 位,那么 3 号槽位 011 将会被 rehash 到 3 号槽位和 11 号槽位,也就是说该槽位链表中大约有一半的元素还是 3 号槽位,其它的元素会放到 11 号槽位, 11 这个数字的二进制是 1011 ,就是对 3 的二进制 011 增加了

一个高位 1,如下图

抽象一点说,假设开始槽位的二进制数是 xxx ,那么该槽位中的元素将被 rehash 到 0xxx 和 1xxx(xxx+8) 中。如果字典长度由 16 位扩容到 32 位,那么对于二进制槽位 xxxx 中的元素将被 rehash 到 0xxxx 和 1xxxx(xxxx+16) 中

对比扩容缩容前后的遍历顺序 ,如下图

观察这张图,我们发现 采用高位进位加法的遍历顺序,无论是扩容还是缩容,rehash 后的槽位在遍历顺序上是相邻的

假设当前要即将遍历 110 这个位置 ( 橙色 )

扩容后,当前槽位上所有的元素对应的新槽位是 0110 和 1110( 深绿色 ) ,也就是在槽位的二进制数增加一个高位 0 或 1 。这时我们可以直接从 0110 这个槽位开始往后继续遍历,而 0110 槽位之前的所有槽位都是已经遍历过的,这样就可以避免扩容后对已经遍历过的槽位进行重复遍历

假设当前要即将遍历 110 这个位置 ( 橙色 )

缩容后,当前槽位所有的元素对应的新槽位是 10( 深绿色 ) ,也就是去掉槽位二进制最高位。这时我们可以直接从 10 这个槽位继续往后遍历,10 槽位之前的所有槽位都是已经遍历过的,这样就可以避免缩容的重复遍历。不过缩容还是不太一样,它会对图中 010 这个槽位上的元素进行重复遍历,因为缩融后 10 槽位的元素是 010 和 110 上挂接的元素的融合, 注意:这也是为什么scan命令可能会返回重复key的根本原因!

渐进式 rehash

Java 的 HashMap 在扩容时会一次性将旧数组下挂接的元素全部转移到新数组下面。如果 HashMap 中元素特别多,线程就会出现卡顿现象

Redis 为了解决这个问题,它采用渐进式 rehash 。它会同时保留旧数组和新数组,然后在定时任务中以及后续对 hash 的指令操作中渐渐地将旧数组中挂接的元素迁移到新数组上。这意味着要操作处于 rehash 中的字典,需要同时访问新旧两个数组结构。如果在旧数组下面找不到元素,还需要去新数组下面去寻找。scan也需要考虑这个问题,对与 rehash 中的字典,它需要同时扫描新旧槽位,然后将结果融合后返回给客户端。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-02-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农编程进阶笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis®
腾讯云数据库 Redis®(TencentDB for Redis®)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档