简介如今,支持HBase的现代产品对HBase的读写性能的期望越来越高。理想情况下,HBase还希望在不影响其可靠持久存储的情况下享受内存数据库的速度。社区贡献者在HBase2.0中引入了一种名为Accordion的新算法,这促使Hbase朝着其理想目标又迈出了重要的一步。HbaseRegionServer负责将数据划分到多个Region。RegionServer的内部(垂直)可扩展性对于最终用户体验和整体系统利用率至关重要。Accordion算法通过更好地利用RAM进一步提高了RegionServer的可扩展性。Accordion算法可以在内存中容纳更多的数据,减少写入磁盘的频率。这正是我们在很多场景下所需要的。第一,HBase的磁盘占用和写放大从而降低;其次,更多的数据读写将直接从RAM中获取,更少的读写是基于磁盘I/O。换句话说,HBase的性能得到了提升。在2.0版本之前,这些不同的指标不能同时满足,相互制约。使用手风琴算法,它们同时得到改进。Accordion的灵感来自于HBase的LSM树设计模式。一个HBase区域存储为一系列查找键值映射。最上面是一个可变内存存储,叫做MemStore,它接收来自***Put的数据。其余的是称为HFiles的不可变HDFS文件。一旦MemStore已满,它将被刷新到磁盘,创建一个新的HFile。HBase采用多版本并发控制,即MemStore将所有修改的数据作为独立的版本存储。因此,一条数据的多个版本同时驻留在MemStore和HFile层。当读操作进来时,Hbase会先扫描BlockCache中的Hfile,获取最新的版本数据。为了减少磁盘访问次数,HFile在后台异步压缩合并。这个过程实际上会清除删除的数据并合并小的HFile文件。LSM树通过将随机应用程序级I/O转换为顺序磁盘I/O来提供出色的写入性能。但是,传统设计不会尝试压缩内存中的数据。这是历史原因造成的:在LSM树设计的时候,RAM还是非常稀缺的资源,所以MemStore的容量很小。由于硬件环境不断变化,RegionServer管理的整个MemStore可能有几G,这给Hbase优化留下了很大的空间。Accordion算法将LSM原理重新应用于MemStore,以在数据仍在RAM中时消除冗余和其他开销。这样做可以降低刷新到HDFS的频率,从而减少写入放大和整体磁盘占用空间。随着刷新次数的减少,MemStore写入磁盘的频率降低,因此写入性能提高。磁盘上的数据更少也意味着块缓存的压力更小、故障率更高以及读取响应能力更好。***,更少的磁盘写入也意味着后台发生的压缩更少,即读写周期会更短。总而言之,内存压缩算法的作用可以看作是让整个系统运行得更快的催化剂。目前Accordion提供两种级别的内存压缩:基本级别和急切级别。前者是适用于所有数据更新模式的通用优化,后者对于数据流量大的应用非常有用,例如生产-消费队列、购物车、共享柜台等。所有这些用例都会频繁更新rowkey,生成多个冗余版本的数据,而手风琴算法将在这些情况下发挥其价值。但另一方面,eager-level压缩优化可能会导致更大的计算开销(更多的内存副本和垃圾收集),这可能会影响写入数据时的响应时间。如果使用MemStore-LocalAllocationBuffer(MSLAB)配置启用MemStore,开销会很高。因此不建议将此配置与eager-level压缩结合使用。如何使用内存压缩可以在全局和列族级别进行配置。支持三个级别的配置:none(传统实现)、basic和eager。默认情况下,所有表都是基本的内存压缩。您可以在hbase-site.xml中覆盖此全局配置,如下所示:hbase.hregion.compacting.memstore.type>/property>您还可以在HBaseshell中单独配置每个列族,如下所示:create'',{NAME=>'',IN_MEMORY_COMPACTION=>''}性能提升我们使用流行的YahooCloudServices基准测试YCSB对HBase进行了广泛测试。我们的实验使用了100-200GB的数据集并执行了各种具有代表性的工作负载。结果表明,手风琴算法带来的性能增益得到了显着提升。重尾(Zipf)分布:第一个实验执行工作负载,其中行键遵循大多数现实场景中出现的Zipf分布。在这种情况下,Accordion在100%的操作都是写操作的情况下实现了写放大降低30%,写吞吐量提高20%,GC降低22%。当50%的操作为读取时,尾部读取延迟减少12%。Uniformdistribution:第二个实验的工作负载是所有rowkeys均匀分布。在这种情况下,在100%写入时,Accordion的写入放大减少了25%,写入吞吐量增加了50%,GC减少了36%。尾读取延迟不受影响(这是预期的,因为没有本地化)。Accordion如何工作高级设计Accordion引入了CompactingMemStore,这是一种在内部使用压缩策略的MemStore实现。与默认的MemStore将所有数据存储在一个单一的数据结构中相比,Accordion将其作为一系列段进行管理。最新的段,称为活动段(segment),是可变的,用于接收Put操作。一旦达到溢出条件(默认情况下,32MB-MemStore大小的25%),活动段将移入管道(内存管道)并设置为不可变段,我们称之为内存刷新(在-内存刷新))。获取操作通过扫描这些段和HFile来获取数据(后者通过块缓存访问,就像HBase一样)。CompactingMemStore可能会不时地在后台合并多个不可变段,创建更大更紧凑的段。因此,管子是“会呼吸的”(膨胀和收缩),类似于手风琴的风箱,所以我们也将Accordion译为手风琴。当RegionServer决定将一个或多个MemStore刷新到磁盘以释放内存时,它会考虑将CompactingMemStore中已经移入管道的段刷新到磁盘。基本原理是延长MemStore的生命周期以有效地管理内存以减少整体I/O。当flush发生时,pipeline中的所有segment都会被移除形成一个snapshot,合并流式形成一个新的HFile。图1显示了CompactingMemStore的结构与传统设计的对比。分段结构:与默认的MemStore类似,CompactingMemStore在cellstore之上维护一个索引,以允许通过key进行快速搜索。不同之处在于,MemStore索引是作为Java跳表(ConcurrentSkipListMap)实现的,这是一种用于管理大量小对象的动态但奢侈的数据结构。CompactingMemStore在不可变段索引之上实现高效且节省空间的平面布局。这种通用优化有助于所有压缩策略减少RAM开销,使数据几乎不存在。将段添加到管道后,CompactingMemStore会将其索引序列化为称为CellArrayMap的有序数组,可以快速进行二进制搜索。CellArrayMap支持从Java堆直接分配单元和自定义分配MSLAB(堆内或堆外)。实现差异是通过索引引用的KeyValue对象抽象出来的(图2)。CellArrayMap本身总是分配在堆上。压缩算法内存压缩算法在管道中的段上维护一个单一的扁平化索引。这样的设计节省了存储空间,尤其是当数据项较小时,可以及时将数据刷入磁盘。单个索引使搜索能够发生在单个空间中,从而减少尾部读取延迟。当一个活动段被刷新到内存时,它被排入压缩管道并立即触发异步合并调度程序。此计划任务将同时扫描管道中的所有段(类似于磁盘上的压缩)并将它们的索引合并为一个。基本压缩策略和急切压缩策略之间的区别在于它们处理单元格数据的方式。基本压缩不会消除冗余数据版本以避免物理复制;它只是重新排列对KeyValue对象的引用。Eagercompression则相反,过滤掉冗余数据。但这是以额外的计算和数据迁移为代价的。例如,在MSLAB内存中,幸存的单元被复制到新创建的MSLAB中。当数据高度冗余时,压缩开销的成本变得有意义。未来的压缩可能会在basic和eager压缩策略之间实现自动选择。例如,该算法可能会在一段时间内尝试急切压缩,并根据传递的值(例如,要删除的数据的百分比)安排下一次压缩。这种方法减轻了系统管理员的先验决定并适应不断变化的访问模式。结束语在本文中,我们介绍了Accordion的内存压缩算法的基本原理、配置、性能增益和一些细节。如有错误,请多多指教。