前言
这篇论文的读后感是我作为本科课程期间的一门大作业课程所需要完成的课外实践内容。如果能够对大家有所帮助就好,不过这一篇主要作为个人的小总结。提前告知,还望海涵。
HotRing策略背景
哈希索引是当前阿里公司在KVSes的中使用的最流行的内存结构,特别是当范围查询不需要上层应用程序时,由于原有哈希表的设计,访问此时应该是:
N(总数)=1+L/2 //L是链表长度
L=N/B //N是总的item数目,B是哈希桶数目
其次,由于同样也要寻找响应items前提下的哈希桶,所以要在最前面+1。所以在已有的的哈希设计下,如何让热点数据长期处于链表头部,那么必然就会将访问复杂度贴近于O(1)。那么就可以想到初步想要解决热点感知的本质——即动态的感知热点并且将热点靠近至指针。
HotRing所要解决的问题
那么想到完善的结构,那么我们就需要结合实际进一步完善HotRing的数据结构:
1、热点的转移。在大数据下,热点的转移是随着时间变化的。访问模式是随着时间变动的。那么我们需要在系统中添加一种跟踪热量的方法。
2、并发访问。在操作系统中,每个热点都被大量的并发访问请求,如何在热点item仍然保持令人满意的性能,这对读写操作都需要有高并发的支持。
3、高效率,即本质上将感知热点靠近头指针。
HotRing设计概览
HotRing是一个环状的链式结构,将一种单链表编程有序环结构在数据结构并不能成为理想的O(1)模型,但是我们只需要将性能访问提升的更多即可。
有序的环哈希索引
无序的环状链要使用额外的哈希表存储已遍历过的item,或者使用双指针增加遍历的范围,若重合就可以进行返回。比如如下:
如果item(a)存在,那么既满足
item(a)=item(x); //item(x)为当前指针指向的item信息
那么当item不存在时,就不存在。因为我们找不到item(a),那么应该就是:
item(x-1)<item(a)<item(x) ||
item(a)<item(x)<item(x-1) ||
item(x)<item(x-1)<item(a)
热点漂移识别
热点的漂移识别在于解决识别热点并且在热点移位中及时调整头指针。那么就需要准确性和及时性。准确性在于将头指针指向hot item的比例;而及时性在于多少延迟之后能够将头指针的指向变换成新的热点。
这里HotRing使用的是一种简单的随机移动策略。
随机移动策略
随机策略在于HotRing采用周期性将头指针指向本次查询到的item。假设设置一个周期为3,那么每3次查询到热点时就要进行判断:
- 如果头指针指向了这个热点:不发生调整
- 如果头指针没有指向这个热点:将指针指向这个热点
这种策略保留了较少的反应延迟(即设置一个周期来小范围进行热点搜寻),但精度相对较低(小范围搜寻的弊端)。但是这种随机策略能够很直接的将热点和cold item区分出来并且发生调整。当然,这个方案在环中存在多个hot item时表现并不好。
统计样本策略
我们设置一个Total Counter用来对HotRing头指针对哈希桶下的环结构命中次数。而在每个环结构中的item设置一个Counter来代表这个item的命中次数。
统计样本在于,HotRing周期性地进行样本分析,每次分析起始时,Total Counter和Counter均为0,在一定时间后,根据Total Counter和每个item的Counter值决定使用哪个item作为头节点。
公式如下:
这个时候,对于多hot item统计样本策略更加准确,并且能够应用更广泛的场景。
item节点设计
在HotRing中,我们设置一个如上图所示的节点结构。
比如我们在搜寻item时,我们在item添加了一个tag。此时判断语句变成:
item(a)=(tag(a),key(a))
这种设计在于不采用全部遍历链表所有元素,它可以提前终止遍历。此时平均情况下查找item此时也是达到(n/2)+1,但是头指针的优化使得查找往O(1)方向靠近。
在统计样本时,我们要使用一个Counter来记录该item被命中的次数。此时我们在item中设置一个14bit的Counter,除此之外,由于现代物理机可以使用64位原子比较和交换操作进行更新,我们用一个Counter(15bit)、address(48bit)、Occupied(1bit)和Rehash(1bit)进行构建。
热点继承
热点继承中,我们要将头结点的指针移动到另一个头节点上。随机的指向会大概率指向冷数据。这将导致热点识别的策略频繁触发。进一步导致热点性能下降。
如果头节点发生了更新,但是由于更行到了新的热点,它必然需要有更大可能被读取到。那么此时就直接指向这个热点。当头节点删除时,那么就将头指针移至下一项即可。
并发操作
并发操作要求在于首先应该是一个无锁状态。不然在有锁条件下要走的一个加工流程周期大大增加,不利于系统的性能。
此时,确定了无锁机制下,头指针的移动会使得无锁设计更加复杂:
- 一方面,头指针移动可能与其他线程并发。因此,我们需要考虑头指针移动和其他修改操作的并发性,防止指针指向无效项。
- 另一方面,当我们删除或更新一个项目时,我们需要检查头部指针是否指向该项目。如果是这样,我们需要正确而巧妙地移动头指针。
在HotRing,一味的插入的新数据到达时,环中的冲突项数量继续增加,导致每次访问都要遍历更多的项。此时在HotRing提出一种无锁重哈希策略。
在HotRing哈希表的扩容时,哈希槽的增加,环状链表也需要将item分配到新的环中。HotRing使用了访问开销(即检索项的平均内存访问次数)来触发重散列。所以HotRing面临以下三个步骤:
1、初始化:
HotRing创建了一个后端rehash线程。线程通过共享标签的最高位来初始化新的哈希表。这个新的哈希表的大小是旧哈希的两倍。
在计算一个item的哈希值时,值的前k位用作哈希表定位,后t位来作为tag。那么当哈希表扩容2倍,由于扩容后再新表有两个新的头指针,此时需要多占用k+1位哈希值。HotRing根据标签范围划分数据,假设哈希值有n位,那么标记的范围在于:
r
a
n
g
e
=
2
∗
t
range=2*t
range=2∗t
扩容后,因为根据哈希表和tag的关系,扩容两倍时,tag值应该是缩小成t-1位。(在计算一个item的哈希值时,值的前k位用作哈希表定位,后t位作为tag。k+1就意味着t-1)。
那么扩容后范围为2^(t-1)
。此时正好将环状链表一分为二,即[0,range/2)和[range/2,range)。
在这么一个初始化的线程中,我们还会创建一个rehash节点,这包含着两个子rehash item。我们能够看到,item有一个rehash的1bit空间,这个地方我们可以引出0和range/2,后续就分别作为两个新环头结点。具体内容如下:
2、分裂阶段
rehash线程将两个rehash item放置环中并作为tag范围和遍历的边界。放置完成后便生效。整个过程大部分操作都是通过无锁的CAS完成的,并且开销会相对较低。
3、删除阶段
rehash线程需要确认所有的旧哈希表访问都已经结束,进而先删除旧哈希表,然后清理所有的rehash节点。
当所有访问结束时,重散列线程可以安全地删除旧表,然后重新散列节点。注意,过渡时期只会阻塞重散列线程,而不会阻塞访问线程。
总结
HotRing适应场景在于热点集中,这要求可能要形成一个宣传好的品牌前提,在这时候,HotRing设计特点如下:
1、将热点数据排列在链表最靠前位置,尽量缩短访问路径。
2、设计环状结构,实现头指针变更但不需要改变item变动。
3、实现随机策略和统计样本策略来处理热点数据、热点漂移。
4、无锁的、原子不可拆分的CAS操作
5、巧妙的进行重哈希,而且并没有数据迁移,只是嵌入两个rehash item并进行分裂。
引用
[1]陈继强,陈亮,王胜,朱国云,陈继强,孙媛媛,刘欢,李菲菲,阿里巴巴集团.《HotRing: A Hotspot-Aware In-Memory Key-Value Store》
[2]潇公子.《中国电信正式发布天翼云操作系统 CTyunOS:基于华为欧拉 openEuler 开源系统》(https://www.ithome.com/0/586/242.htm)
[3]人民日报客户端.《最新!华为发布openEuler欧拉操作系统,欧拉系统是什么?》(https://cbgc.scol.com.cn/news/2120006)
[4] zhisheng.《HotRing——热点感知的哈希冲突解决方案》(https://cloud.tencent.com/developer/user/1130324)