redis系列——scan机制:高位进位加法

key的存储结构

redis所有的key都存储在一个很大的字典中,这个字典的结构就是hashtable。

hashtable的结构之前有提过,简单来说它就是一维数组+二维list结构,如下:

第一维数组的大小总是2^n(n>=0),扩容一次,数组的空间大小就加倍,即n++。

高位进位加法

之前提到可替换keys的scan指令,从字面上来讲,scan就是扫描指令,所谓扫描,就是遍历所有的元素,那么遍历的顺序是怎样的呢?是从小到大,还是从大到小?

注意:这里说的"遍历顺序"针对的是hashtable的一维数组,二维链表就是简单的链表遍历。

在redis的实现中,采用了一种神奇的遍历方式:高位进位加法。

什么是高位进位加法?其实是相对于低位进位加法来说的。二者的区别如上图所示,低位进位加法的进位方向是由右向左,而高位进位加法的进位方向是由左向右。

我们以n=4,hashtable的桶数(一维数组的长度)为16为例,通过以下对比表格来说明下高低位的区别。

桶下标 低位进位加法 高位进位加法
0 0000 (0) 0000 (0)
1 0001 (1) 1000 (8)
2 0010 (2) 0100 (4)
3 0011 (3) 1100 (12)
4 0100 (4) 0010 (2)
5 0101 (5) 1010 (10)
6 0110 (6) 0110 (6)
7 0111 (7) 1110 (14)
8 1000 (8) 0001 (1)
9 1001 (9) 1001 (9)
10 1010 (10) 0101 (5)
11 1011 (11) 1101 (13)
12 1100 (12) 0011 (3)
13 1101 (13) 1011 (11)
14 1110 (14) 0111 (7)
15 1111 (15) 1111 (15)

低位进位加法的遍历顺序为:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15。

高位进位加法的遍历顺序为:0,8,4,12,2,10,6,14,1,9,5,13,3,11,7,15。

我们知道,用scan替换keys指令后,之所以高效,是因为不再锁住了整个字典,而是可以通过指令参数指定读取的cursor和count,相当于把大锁替换为小锁,分页读取。

但是换成小锁,引出一个问题:在遍历的同时,会有其他的线程对hashtable进行增删改,进而可能触发hashtable的扩容或者缩容。如果按照低位进位加法的方式进行遍历,将很容易出现元素的重复读取或者遗漏,导致之前的已完成的遍历内容变得不可靠。高位进位加法遍历,就能够很好地避免这个问题。

请参看下图:

hashtable是按照2^n的长度进行扩容,例如原来的数组长度为8位,扩容后将变更为16位,原来16位的长度,缩容后的长度为8位。而后所有的元素要进行rehash操作,将元素的hash运算值按照新的长度进行取模mod运算。

从图示可以发现,rehash后的桶在高位进位加法遍历顺序上是相邻的。例如110(6)这个桶,在扩容后,rehash的桶为0110(6)和1110(14),这两个桶在高位进位加法遍历顺序上是相邻的;反之,0110(6)和1110(14)这两个桶,在缩容后,rehash的桶为110(6)。

奇妙之处就在这里:也就是在rehash前遍历过的元素,rehash之后,按照新的数组长度,继续从之前遍历的断点处继续往下遍历,将不会出现大面积的重复遍历或者遗漏。

这里之所以说不会"出现大面积"而不是"不出现",是因为在缩容的情况下,有可能出现重复扫描。例如:当前即将扫描1110(14)桶的数据,发生了缩容,则会从缩容后的110(6)桶开始扫描,而这个桶内的元素包含了缩容前的0110(6)中的元素,结果集中就会出现重复元素了。

总结

本文介绍了scan遍历使用的高位进位加法,这是redis实现hashtable高效扫描的核心机制,值得推敲学习。

相关推荐
跟着珅聪学java37 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
morris1316 小时前
【redis】redis实现分布式锁
数据库·redis·缓存·分布式锁
hycccccch6 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
bobz9657 小时前
k8s 怎么提供虚拟机更好
后端
bobz9657 小时前
nova compute 如何创建 ovs 端口
后端
用键盘当武器的秋刀鱼7 小时前
springBoot统一响应类型3.5.1版本
java·spring boot·后端
爱的叹息8 小时前
spring boot集成reids的 RedisTemplate 序列化器详细对比(官方及非官方)
redis