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

相关推荐
oliver_sys_log39 分钟前
RocketMQ 4.5.1 延迟消息"发送成功但消费不到"排查分析报告
后端·rocketmq
码上观网41 分钟前
VXLAN 中的 ARP 代答:从原理到 SONiC 代码实现
后端
计算机毕设定制辅导-无忧学长1 小时前
《基于SpringBoot的马术俱乐部管理系统》
java·spring boot·后端
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 12 章 JDBC 与事务 JDBC 事务管理 阅读笔记 33
spring boot·笔记·后端
学长毕业设计2 小时前
基于SpringBoot的民间艺术传承管理系统(源码+文档+讲解视频)
java·spring boot·后端
小蒜学长2 小时前
基于Springboot+Vue的环保行动志愿者招募系统设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
用户233376852182 小时前
Ubuntu上fpm多架构打包实战
后端
JavaGuide2 小时前
轻量开源版 IDEA 来了!
前端·后端
Json____3 小时前
springboot开发高校选课管理系统:从零构建前后端分离的全栈实践
java·spring boot·后端·毕业设计·毕设·wwwoop.com
站大爷IP3 小时前
Python的列表推导式把我写崩了,原来圆括号和方括号不是一回事
后端