HBase学习之Region

Region是RS上的基本数据服务单位,用户表格由1个或者多个Region组成,根据Table的Schema定义,在Region内每个ColumnFamily的数据组成一个Store。每个Store内包括一个MemStore和若干个StoreFile(HFile)组成。如图(3)所示。本小节将介绍Store内的MemStore、StoreFile(HFile)的内部结构与实现。

Region-Store结构图

1. MemStore原理与实现分析

MemStore是一个内存区域,用以缓存Store内最近一批数据的更新操作。对于Region指定的ColumnFamily下的更新操作(Put、Delete),首先根据是否写WriteAheadLog,决定是否append到HLog文件,然后更新到Store的MemStore中。显然,MemStore的容量不会一直增长下去,因此,在每次执行更新操作时,都会判断RS上所有的MemStore的内存容量是否超过阈值,如果超过阈值,通过一定的算法,选择Region上的MemStore上的数据Flush到文件系统。更详细的处理流程图如图(4)。

这里写图片描述

MemStore类内的重要的成员变量:

volatile KeyValueSkipListSet kvset;//内存中存放更新的KV的数据结构
volatile KeyValueSkipListSet snapshot;//Flush操作时的KV暂存区域
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();//Flush操作与kvset之间的可重入读写锁
final AtomicLong size;//跟踪记录MemStore的占用的Heap内存大小
TimeRangeTracker timeRangeTracker;//跟踪记录kvset的最小和最大时间戳
TimeRangeTracker snapshotTimeRangeTracker;//跟踪记录snapshot的最小和最大时间戳
MemStoreLAB allocator;//实际内存分配器
*注意 KeyValueSkipListSet是对于jdk提供的ConcurrentSkipListMap的封装,Map结构是<KeyValue,KeyValue>的形式。Concurrent表示线程安全。SkipList是一种可以代替平衡树的数据结构,默认是按照Key值升序的。对于ConcurrentSkipListMap的操作的时间复杂度平均在O(logn),设置KeyValue. KVComparator比较KeyValue中Key的顺序。*

写入MemStore中的KV,被记录在kvset中。根据JVM内存的垃圾回收策略,在如下条件会触发Full GC:

  • 内存满或者触发阈值。
  • 内存碎片过多,造成新的分配找不到合适的内存空间

RS上服务多个Region,如果不对KV的分配空间进行控制的话,由于访问的无序性以及KV长度的不同,每个Region上的KV会无规律地分散在内存上。Region执行了MemStore的Flush操作,再经过JVM GC之后就会出现零散的内存碎片现象,而进一步数据大量写入,就会触发Full-GC。图(5)显示这种假设场景的内存分配过程。

这里写图片描述

为了解决因为内存碎片造成的Full-GC的现象,RegionServer引入了MSLAB(HBASE-3455)。MSLAB全称是MemStore-Local Allocation Buffers。它通过预先分配连续的内存块,把零散的内存申请合并,有效改善了过多内存碎片导致的Full GC问题。
MSLAB的工作原理如下:

  • 在MemStore初始化时,创建MemStoreLAB对象allocator。
  • 创建一个2M大小的Chunk数组,偏移量起始设置为0。Chunk的大小可以通过参数hbase.hregion.memstore.mslab.chunksize调整。
  • 当MemStore有KeyValue加入时,maybeCloneWithAllocator(KeyValue)函数调用allocator为其查找KeyValue.getBuffer()大小的空间,若KeyValue的大小低于默认的256K,会尝试在当前Chunk下查找空间,如果空间不够,MemStoreLAB重新申请新的Chunk。选中Chunk之后,会修改offset=原偏移量+KeyValue.getBuffer().length。chunk内控制每个KeyValue大小由hbase.hregion.memstore.mslab.max.allocation配置。
  • 空间检查通过的KeyValue,会拷贝到Chunk的数据块中。此时,原KeyValue由于不再被MemStore引用,会在接下来的JVM的Minor GC被清理。

注意 设置chunk的默认大小以及对于KeyValue大小控制的原因在于,MSLAB虽然会降低内存碎片造成的Full-GC的风险,但是它的使用会降低内存的利用率。如果超过一定大小的KeyValue,此时该KeyValue空间被回收之后,碎片现象不明显。因此,MSLAB只解决小KV的聚合。

MSLAB解决了因为碎片造成Full GC的问题,然而在MemStore被Flush到文件系统时,没有reference的chunk,需要GC来进行回收,因此,在更新操作频繁发生时,会造成较多的Young GC。
针对该问题,HBASE-8163提出了MemStoreChunkPool的解决方案,方案已经被HBase-0.95版本接收。它的实现思路:

  • 创建chunk池来管理没有被引用的chunk,不再依靠JVM的GC回收。
  • 当一个chunk没有引用时,会被放入chunk池。
  • chunk池设置阈值,如果超过了,则会放弃放入新的chunk到chunk池。
  • 如果当需要新的chunk时,首先从chunk池中获取。

根据patch的测试显示,配置MemStoreChunkPool之后,YGC降低了40%,写性能有5%的提升。如果是0.95以下版本的用户,可以参考HBASE-8163给出patch。

思考 通过MemStore提供的MSLAB和MemStoreChunkPool给出的解决方案,可以看出在涉及到大规模内存的Java应用中,如何有效地管理内存空间,降低JVM GC对于系统性能造成的影响,成为了一个研究热点。整体上来说,一是设置与应用相适应的JVM启动参数,打印GC相关的信息,实时监控GC对于服务的影响;二是从应用程序设计层面,尽可能地友好地利用内存,来降低GC的影响。

在ChunkPool就是帮助JVM维护了chunk信息,并把那些已经不再MemStore中的数据的chunk重新投入使用。这样就可以避免大量的YGC。

2、MemStore参数控制原理与调优

对于任何一个HBase集群而言,都需要根据应用特点对其系统参数进行配置,以达到更好的使用效果。MemStore作为更新数据的缓存,它的大小及处理方式的调整,会极大地影响到写数据的性能、以及随之而来的Flush、Compaction等功能。这种影响的原因在于以下两个方面。

  • RS全局的MemStore的大小与Region规模以及Region写数据频度之间的关系。
  • 过于频繁的Flush操作对于读数据的影响。

这其中涉及到的可调整的参数如下表。

参数名称:hbase.regionserver.global.memstore.upperLimit
参数含义:RS内所有MemStore的总和的上限/Heap Size的比例,超过该值,阻塞update,强制执行Flush操作。
默认值:0.4

参数名称:hbase.regionserver.global.memstore.lowerLimit
参数含义:执行Flush操作释放内存空间,需要达到的比例。
默认值:0.35

参数名称:hbase.hregion.memstore.flush.size
参数含义:每个MemStore占用空间的最大值,超过该值会执行Flush操作。
默认值:128MB

参数名称:hbase.hregion.memstore.block.multiplier
参数含义:HRegion的更新被阻塞的MemStore容量的倍数。
默认值:2

参数名称:hbase.hregion.preclose.flush.size
参数含义:关闭Region之前需要执行Flush操作的MemStore容量阈值。
默认值:5MB

对于上述参数理解:
(1)RS控制内存使用量的稳定。
例如,假设我们的RS的内存设置为10GB,按照以上参数的默认值,RS用以MemStore的上限为4GB,超出之后,会阻塞整个RS的所有Reigon的请求,直到全局的MemStore总量回落到正常范围之内。
以上涉及到cacheFlusher在MemStore总量使用超过上限时,选择Region进行Flush的算法,由MemStoreFlusher.flushOneForGlobalPressure()算法实现。算法的处理流程如下。

SortedMap<Long,HRegion> regionsBySize =
server.getCopyOfOnlineRegionsSortedBySize();//从RS上获取在线的Region,以及它们在MemStore上使用量,并按照MemStore使用量作为Key,降序。
Set excludedRegions = new HashSet();//记录尝试执行Flush操作失败的Region
…
HRegion bestFlushableRegion = getBiggestMemstoreRegion(
regionsBySize, excludedRegions, true);//选出storefile个数不超标、当前MemStore使用量最大的Region
HRegion bestAnyRegion = getBiggestMemstoreRegion(
regionsBySize, excludedRegions, false);//选出当前MemStore使用量最大的Region

步骤1:RS上在线的Region,按照当前MemStore的使用量进行排序,并存储在regionsBySize中。
步骤2:选出Region下的Store中的StoreFile的个数未达到hbase.hstore.blockingStoreFiles,并且MemStore使用量最大的Region,存储到bestFlushableRegion。
步骤3:选出Region下的MemStore使用量最大的Region,存储到bestAnyRegion对象。
步骤4:如果bestAnyRegion的memstore使用量超出了bestFlushableRegion的两倍,这从另外一个角度说明,虽然当前bestAnyRegion有超过blockingStoreFiles个数的文件,但是考虑到RS内存的压力,冒着被执行Compaction的风险,也选择这个Region作为regionToFlush,因为收益大。否则,直接选择bestFlushableRegion作为regionToFlush。
步骤5:对regionToFlush执行flush操作。如果操作失败,regionToFlush放入excludedRegions,避免该Region下次再次被选中,然后返回步骤2执行,否则程序退出。

(2)设置两个limit,尽可能减少因为控制内存造成数据更新流程的阻塞。
当RS的MemStore使用总量超过
(Heap*hbase.regionserver.global.memstore.lowerLimit)
的大小时,同样会向cacheFlusher提交一个Flush请求,并以(1)中Region选择算法,对其进行Flush操作。与(1)不同,这个过程中RS不会阻塞RS的写请求。
因此,在生产环境中,我们肯定不希望更新操作被block,一般会配置(upperLimit –lowerlimit)的值在[0.5,0.75]之间,如果是应用写负载较重,可以设置区间内较大的值。

3、 StoreFile—HFile

下篇讲解

4 、Compaction对于服务的影响

详情请参考:
HBase Compaction的前生今世-身世之旅
HBase Compaction的前生今世-改造之路

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值