一、主要思想
Rating这篇论文主要引入了缓存中热点数据的高效查找。对于一般的系统而言,热点数据一般都会放在Redis等缓存中间件中。但是当热门数据中存在着一些更热门的数据,即缓存的这些热门数据中,有一些数据的访问量大,其他的少一点。面临严重的数据访问倾斜时,如何提高热点的访问效率呢?
Rating提出了"热环"的概念。即将原始的哈希表使用拉链法来解决冲突的方式改为将链表构造成一个循环链表。同时设置一个head指针指向当前环上的最热门数据。
这样更改结构后,就会容易造成查找某个元素,如果查找不到会形成无限循环,Rating将这个循环链表的元素按照某个分值来排序,如果A的分值<待查元素的,且A得后继分值大于待查元素,则查找失败。
那Rating又是如何保证有序的呢?论文中提到,不会对整个链表重新排列,所以,我认为是在将元素插入时就按照排序规则找到该元素的位置,然后将其插入。
此外Rating还面临两个更关键的问题:热点移动如何检测、head指针如何灵活准确且原子性的指向最新的热点元素。即热点移动问题和并发问题。
对于热点移动,Rating提出了两种方案,分别是随机游走策略和统计抽样策略。
对于并发操作,Rating主要使用了CAS无锁操作。
二、具体细节
2.1 热点检测策略
-
随机游走策略
头指针周期移动,指向一个热点项,不依赖任何历史元数据。每个线程维护一个变量,记录该线程处理了多少个请求,如果当前处理的是第R个请求,且请求访问的元素刚好就是头指针指向的元素,则不移动头指针,如果第R个请求访问的元素不是头指针所指向的元素,则将头指针移动到第R次请求访问的元素处。这种策略,如果R很小,则头指针频繁移动,如果R很大,则对于热点数据的检测很不灵敏。
-
统计抽样策略
头指针格式:
active total_counter address 1bit 15bit 48bit 控制统计采样的标识 当前热环总的访问次数 环的头指针所指向的元素地址 元素项Next部分的格式(即当前项记录下一项的一些信息):
rehash occupied counter next 1bit 1bit 14bit 48bit 控制rehash的标志 用于并发控制,CAS 该元素项的访问次数 下一个元素项的地址 具体流程:
每个线程维护一个变量,记录该线程处理了多少个请求,当处理第R次请求时,如果访问的元素刚好是头指针指向的元素,则不移动头指针且不发生统计采样。如果不是,则启动统计采样,然后移动头节点。采用的数目也是采集R个请求,刚好到下一次判决是否移动头节点。如果下一次是需要移动头节点的,并不是将头节点直接移动到第R次请求访问的元素位置,而是移动到基于上一轮采样所计算出来的节点。采样是会将Head指针的active开启,表示当前环正在采样,同时防止并发操作,导致环多次开启采样。同时每个数据项被访问后,其对应的counter都会加1。当R次采样完成后,关闭active,然后遍历环,计算每一项的的访问频率,同时计算收益,将收益最小的项作为新的热点。将头指针指向新的热点。(如果头指针需要转移的话。不需要转移,直接关闭采样即可,相当于上轮的采样无作用。)
2.2 无锁操作
主要通过CAS操作,控制occupied字段来实现并发无锁操作。
2.3 rehash
rehash通过访问开销,即访问热环数据的平均内存访问次数(针对于所有hash环)来决定是否rehash。
分为三步:初始化、分割、删除。
-
初始化
首先会创建一个后台线程,该线程初始化一个size是旧hash表两倍的新hash表。然后新hash表中会有两个head与原来的一个head对应。
元素项的格式:Metadata Tag Rehash occupied counter address 原数据 标签 控制rehash时该项分配到哪个新环 CAS使用 访问次数 下一个元素的地址 此处根据rehash该bit的来区分旧环上的数据放到哪个新环上。(此处该rehash位到底是如何赋值的没看懂)
然后,后台线程创建一个rehashNode,包含两个子Node,两个新的head分别指向这两个子Node,然后根据Tag划分范围,即[0-T/2],[T/2,T]两个范围。
-
分割
根据rehash位分别链接到对应的子Node上。(rehash位是根据tag所属的区间计算的) -
删除
删除rehash Node,删除旧的hash表。不过在此之前,需要保证旧hash表上的访问要全部完成才可以。
参考:https://blog.csdn.net/m0_37055174/article/details/104765586