HotRing: A Hotspot-Aware In-Memory Key-Value Store
Posted helloworldcode
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HotRing: A Hotspot-Aware In-Memory Key-Value Store相关的知识,希望对你有一定的参考价值。
本文主要解决的是基于内存的K-V存储引擎在实际应用中出现的热点问题,设计了一个热点可感知的KV存储引擎,极大的提升了KV存储引擎对于热点数据访问的承载能力。
Introduction
热点问题,可以理解为在一个严重倾斜的工作负载下,频繁的访问和操作某一小部分数据。
如图,是阿里的不同业务中数据访问分布情况,大量的数据访问只集中在少部分的热点数据中。在平常的情况下,百分之五十的用户访问请求只是针对其中百分之一的数据,在一些极端的情况下,当新产品发售后,大量的粉丝疯狂进行抢购下单,业务的访问量基本都聚集在某一小部分数据上,会出现百分之九十的用户请求针对其中百分之一的数据。
目前有一些方法来解决热点数据问题:
- 使用一致性哈希算法将数据分片,分布到多个节点上,分摊热点访问。(Scale out using consistent hashing)
- 将热点数据备份到多个节点上,分摊热点访问。 (Replication in multi-node)
- 客户端缓存:通过预先标记热点,设置客户端层面的缓存。减小键值存储系统的负载压力。(Front-end cache)
第一种和第二种方法中因为节点的添加,会使得系统的成本会大大增加。第三种方法,如果用户请求中涉及到很多数据更新的请求,对客户端层面的缓存的数据需要维护,这个过程实现会变得很复杂。
本篇论文则是从提升单个点对热点数据的处理能力出发(Improve Single node’s ability to handle hotspots),设计了一种热点可感知的键值数据结构,实现在严重倾斜的工作负载下,快速的完成用户的访问请求。
Prior work
现有很多基于内存的键值存储引擎通常采用链式哈希作为索引,这种索引结构对热数据是无法感应到,数据项被随机的分布在链表上。如果图中item3是一个热数据项,他处在某一链表的尾部,需要经过多次内存访问才可以将item3的数据取出来。如果高度倾斜的工作负载下,要通过多次内存访问才可以的得到item3,会使得系统的性能就会很低下。如果可以将热点数据放置在冲突链头部,那么系统对于热点数据的访问将会有更快的响应速度。
Challenges
设计一个热点可感知的索引结构需要解决两个问题:
数据的热度是动态变化的,必须实现动态的热点感知,保证热点时效性。
无锁化处理,基于内存的键值存储引擎性能是很敏感的,要保证高性能就必须支持无锁的并发读/写操作
Design of HotRing
论文提出的索引结构被称作hotring,与传统的哈希结构不同的是,将哈希表中的链式结构改成了环,哈希表中存储的头指针可以指向环中的任意数据项。当链表变成环的时候,以头指针所指的数据项为起点,查找要访问的元素,如果元素不存在,就会一直循环查找下去,因此,作者将该还设计成一个有序环。
在变成有序环的时候,因为表示key大小所用的字节数一般不会少(通常为10-100字节大小),只是简单的对key进行排序,比较会带来巨大的开销。我们构建字典序:order = (tag, key)。先根据tag进行排序,tag相同再根据key进行排序:以itemB举例,链式哈希需要遍历所有数据才能返回read miss。而HotRing在访问itemA与C后,即可确认B read miss。
Hotspot Shift Identification
每段时间用户的访问需求在不断变化,数据的热度是动态变化的,HotRing实现了两种策略来实现周期性的动态识别热点并调整头指针指向。
- 随机移动策略
- 采样分析策略
随机移动策略
每个线程维护一个变量,记录执行了多少次请求,每R次访问,移动头指针指向第R次访问的数据项。若指针已经指向该数据项,则头指针不移动。该策略的优势是, 不需要额外的元数据开销,实现简单,响应速度快。
缺点:
- 若R小,找到热点的时间会很短,但是可能造成频繁的头指针移动。
- 若用户的访问负载倾斜程度小,头指针移动的频率会变高,效率就会降低。
- 难以处理环中多个热点数据。
采样分析策略
使用该策略时,同样的,在R次访问后,若第R次访问的item已经是头指针指向的item,则不启动采样,否则,尝试启动对应冲突环的进行采样。
在介绍采样策略之前,先介绍一下头指针和数据项对应的数据结构。头指针head包括:
- active:1 bit,作为控制采样分析的标识。
- total_counter:15 bits,当前环总共的访问次数。
- address:48 bits,环的头地址(x86-64上目前只使用了48位)。
而环上每一项的next包括(因为现代操作系统所使用的内存地址空间使用64bit,而环中下一项的地址实际只需要48bit即可表示,其余的16bit来控制并发,访问次数等信息):
- rehash:1 bit,作为控制rehash的标识。
- occupied:1 bit,用于并发控制,保证并发访问的正确性。
- counter:14 bits,该项的访问次数。
- address:48 bits,下一项的地址。
现在开始介绍采样分析策略,如果第R次访问数据项并不是头指针指向的数据项,说明热点数据已经发生了变化,这个时候会对冲突环进行采样。采样的过程如下:
- 打开head.active(CAS)
- 后续的请求的访问记录会被记录到头指针的total_count和对应item的的next.count(CAS)采样个数也是R
- 采样结束后,将头指针的采样标记恢复过来。
CAS(Compare And Swap):当要操作内存中某一个变量的时候,会记录下变量中的旧值,通过对旧值进行一系列的操作后得到新值,然后将旧值会与内存中的变量做比较,如果不相同,则说明内存中的值在这期间内被修改过,这时CAS操作将会失败,新值将不会被写入内存。如果相同,使内存中的变量变为新值。
对数据采样结束后,利用已有的信息可以进行判断将哪一个数据项作为头节点。具体过程如下:
- 遍历环,计算每一项的访问频率。(第k项的访问次数nk,环中所有项的访问次数为N,则第k项的访问频率为nk/N)。
- 计算每个节点的收益W,取最小的收益的数据项作为新的热数据项。每一项的受益值的计算公式如下,假设现在求第k项的收益,即计算所有项到该项的距离乘以该项的访问频率,求得每一项的期望值,选择最小的期望值作为新的头节点:
- 使用CAS原子操作将新的头指针指向数据项。
- 重置头指针和数据项的计数器。
Write-Intensive Hotspot with RCU
在一般情况下,对于更新操作,HotRing可以对不超过8字节的数据进行update-in-place原子更新操作,这种情况下,读取和更新被视作一样的操作。但对于超过8个字节的大数据进行更新,hotring则会使用read-copy-update协议,RCU——更新数据的时候,首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个期间数据都是可以随意读的。
对数据采样结束后,利用已有的信息可以进行判断将哪一个数据项作为头节点。具体过程如下:
- 遍历环,计算每一项的访问频率。(第k项的访问次数nk,环中所有项的访问次数为N,则第k项的访问频率为nk/N)。
- 计算每个节点的收益W,取最小的收益的数据项作为新的热数据项。每一项的受益值的计算公式如下,假设现在求第k项的收益,即计算所有项到该项的距离乘以该项的访问频率,求得每一项的期望值,选择最小的期望值作为新的头节点:
- 使用CAS原子操作将新的头指针指向数据项。
- 重置头指针和数据项的计数器。
Write-Intensive Hotspot with RCU
在一般情况下,对于更新操作,HotRing可以对不超过8字节的数据进行update-in-place原子更新操作,这种情况下,读取和更新被视作一样的操作。但对于超过8个字节的大数据进行更新,hotring则会使用read-copy-update协议,RCU——更新数据的时候,首先拷贝一个副本,然后对副本进行修改,最后使用一个回调(callback)机制在适当的时机把指向原来数据的指针重新指向新的被修改的数据,这个期间数据都是可以随意读的。
当更新的数据项是头指针指向的热数据项时,因为要修改前一个数据项的指针,需要遍历整个环来获取头节点的前一项。也就是说,在,导致无法辨别热数据项。论文在这种情况下,更新的只是前一项的计数器,其他项的计数器。
当更新的数据项是头指针指向的热数据项时,因为要修改前一个数据项的指针,需要遍历整个环来获取头节点的前一项。也就是说,在,导致无法辨别热数据项。论文在这种情况下,更新的只是前一项的计数器,其他项的计数器。
以上是关于HotRing: A Hotspot-Aware In-Memory Key-Value Store的主要内容,如果未能解决你的问题,请参考以下文章