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高效扫描的核心机制,值得推敲学习。

相关推荐
牛奶3 分钟前
1秒下单10万次,服务器是怎么扛住的?
大数据·服务器·后端
小强19885 分钟前
为什么小程序中不能使用 window、document 或 jQuery?
后端
楼田莉子10 分钟前
仿Muduo的高并发服务器:LoopThread模块及其ThreadPool模块
linux·服务器·c++·后端·学习
二月龙12 分钟前
微信小程序页面栈限制解析与突破方案
后端
Rust研习社17 分钟前
你为什么总是入门 Rust 失败
开发语言·后端·rust
SamDeepThinking18 分钟前
批评下属不如当场展示解决方案
后端·程序员·团队管理
AskHarries1 小时前
GPT-Image-2(img2)到底能做什么?
后端
Leinwin1 小时前
GPT-5.5 Instant API接入教程:免费额度、速率限制与最佳实践
后端·python·flask
Xidaoapi1 小时前
从零搭建一个AI Agent:Python实战指南
后端
aXin_ya1 小时前
微服务 第十天 (Redis多级缓存)
java·redis·微服务