浅析HBase1.2.0的Split机制

日前,在项目进行过程中,需要把一部分不大不小的数据暂时存起来,所以个人想到的就是放到HBase好了,手上刚好有现成的代码和环境,但是扔到表中发现表从最初的1个Region分裂成了4个Region。

浅析HBase1.2.0的Split机制

而系统在hbase-site.xml文件中Store的大小最大设置的是10G:

    </property>

      <property>

      <name>hbase.hregion.max.filesize</name>

      <value>10737418240</value>(Byte)

</property>

    现在是还没有达到10G就拆分了,由于之前都是在建表的时候预先将Region拆分好了,一般不会遇到自动Split的情况,所以趁机来研究下HBase的Split机制。

 

哪几种情况会触发Split:

  1. 任意一个Store的大小超过了hbase.hregion.max.filesize
  2. Compaction时,某个HFile的大小超过了hbase.hregion.max.filesize
  3. Flush之前,某个Region下的storeFile的数量超过了hbase.hstore.blockingStoreFiles(默认7,目前线上设置100,到达该数量会阻塞写请求),并且请求未超时hbase.hstore.blockingWaitTime(flush会阻塞等到compaction工作完成,达到这个时间之后,会停止阻塞写请求,可以继续写数据;默认90s,建议配小点),会触发Split或者Compaction机制。
  4. Flush之后,判断Store是否太大需要Split
  5. 手动调用Split

Flush之前为什么会调用Split或者Compaction逻辑,看下了感觉HBase好像是想让Region的数量尽快达到regionSplitLimit数量;如果无法进行Split(数据写的太快),先进行一次Compaction…这里暂时不太理解为什么要先Split这么设计,谁知道麻烦告诉我一声呀

浅析HBase1.2.0的Split机制

浅析HBase1.2.0的Split机制

 

ps:Region的数量达到hbase.regionserver.regionSplitLimit的值时,Split机制就不会触发了,所以有些情况下会将这个配置设置为1,不让HBase的自动Split机制触发。

 

HBase中Region信息如下:

浅析HBase1.2.0的Split机制

 

HDFS上Store的存储结构如下:

浅析HBase1.2.0的Split机制

 

HBase1.2.0中的Split策略:

浅析HBase1.2.0的Split机制

对这些Split进行简单的介绍,然后重点分析下IncreasingToUpperBoundRegionSplitPolicy。

 

1.ConstantSizeRegionSplitPolicy

0.94版本之前默认的机制,某个region最大store的大小大于设置阈值之后才会触发切分,store大小为压缩后的文件大小(如果采用压缩的场景)。这里的阈值是一个固定值,不能自适应调整。

Region切分是依据“数据对半”原则,找到该region的最大store的中间长度的rowkey进行split。

 

2. DisabledRegionSplitPolicy

    不使用Region的Split策略,将数据都写到一个Region中

 

3. KeyPrefixRegionSplitPolicy  & DelimitedKeyPrefixRegionSplitPolicy

    这两种拆分Region的策略有点相似,都是在rowKey上做文章。

    前者是通过指定rowKey的前多少位作为前缀做为拆分控制参数,相同的rowKey在进行region split的时候会分到相同的region中。

    后者是根据rowKey中指定分隔字符做为拆分控制参数,显得更加灵活。如rowKey的值为“userid_eventtype_eventid”,且指定了分隔字符串为下划线"_",则DelimitedKeyPrefixRegionSplitPolicy将取RowKey值中从左往右且第一个分隔字符串之前的字符做为拆分串,在该示例中就是“userid”。

 

4. IncreasingToUpperBoundRegionSplitPolicy

    重点分析下这个,这个是HBase1.2.0中默认的Region的Split策略,也是我们项目采用的机制。该类采用的分裂策略如下:

Split Size= min⁡(hbase.hregion.max.filesize,2*regioncount3*Flush Size)

    在同一台RegionServer上,同一张表的region个数的立方乘以Flush Size再乘以二,与hbase.hregion.max.filesize相比取最小者。看下代码就明白了,代码如下:

浅析HBase1.2.0的Split机制

(PS:如果Region数量>100,为了防止数值溢出,直接按照配置项的大小作为划分依据)

 

用我现在的环境来举个例子:

    Flush Size使用的是默认值128M,hbase.hregion.max.filesize设置的是10G,初始时只有一个Region。

    第一次Split: min(10G,2*1*128M)=256M,即某个Store的大小达到256M的时候,Region就会分裂成两个,分裂后会有2个Region。

    第二次Split: min(10G,2*8*128M)=2G,分裂后会有3个Region。

    第三次Split: min(10G,2*27*128M)=10G…从这之后,都是按照10G的大小进行分裂了,某个Regin中某个Store中锁存储的StoreFile大小之和达到10G就Split(Store中的MemoryStore不算,因为这部分在内存里面)。

 

为什么0.94~2.0版本默认策略换成了这个,是因为这种不确定的切分策略可以兼顾到大表和小表,相对于之前固定阈值切分更加灵活点。

 

Region Split流程分析:

    HBase将整个Split的过程封装在一个事务中,如果执行到某一步失败了可以rollback,保证了事务的原子性。每个Regionserver都会维护一个CompactSplitThread线程,所有的compact/split的请求都会交给它来处理。

 

1.进行shouldSplitRegion判断,找到拆分点,发送SplitRequest请求

浅析HBase1.2.0的Split机制

看下拆分点是怎么计算的:

浅析HBase1.2.0的Split机制

默认是使用DefaultStoreFileManager,获取该Region中size最大的那个StoreFile的midKey,所以拆分不一定是整体完全对半拆分。随后会将信息封装在一个SplitRequest提交给Split线程池执行。

 

 

2.执行SplitRequest,拆分Region

浅析HBase1.2.0的Split机制

前边主要是两步,一步是统计下RS上进行了多少次split操作,二是从ZK上获取到读锁(对Metadate加读锁),然后调用execute()真正执行Split:

浅析HBase1.2.0的Split机制

 

2.1创建两个子Region

首先开启拆分事务,标记当前的Region为Spiltting状态,然后通知master监听

浅析HBase1.2.0的Split机制

在内存中记录Splitting过程,保证事务  如果失败则根据这些步骤进行回滚

浅析HBase1.2.0的Split机制

接着在HDFS,partent region目录下创建.split文件夹,并将父Region下线

浅析HBase1.2.0的Split机制

False的意思是,在关闭之前先强制执行一次flush。接着遍历该Region中是所有store,调用其close()

 

2.2真正拆分Region

浅析HBase1.2.0的Split机制

对每一个storeFile进行split,异步调用StoreFileSplitter中的call()方法,分别引用一个storeFile的上半部分和下半部分(根据splitKey来定位),即子Region创建对父Region的storeFile的引用就算split了

浅析HBase1.2.0的Split机制

    从下面的代码可以看出,Split内部并没有真正进行拆分,而是创建Reference来引用文件,Reference有top referencebottom reference之分。

浅析HBase1.2.0的Split机制

创建两个子Region的目录

浅析HBase1.2.0的Split机制

更新meta表信息

浅析HBase1.2.0的Split机制

 

3.打开子Region,对外提供服务,同时更新ZK节点

stepsAfterPONR()方法中涉及到,至此,Region Split流程执行完毕!

 

 

 

个人认为比较重要的点:

    从上面的分析可以看出META表是不会进行Split的;、

并且如果当前Region持有引用(即时一个子Region,且没有发生过Major Compaction,导致父Region一直存在),是不能再进一步进行Split的,这样会导致文件很大!

    参考中附加了一个网上看到的问题,个人没遇到过,但是从流程上这个问题可能是会发生的,后续要注意下如果数据量大一定要预拆分Region,否则就会碰到上面这个文件很大的问题。

 

后记:

    后续有空的话分析下HBase Compaction流程还有其他流程,希望能在解决的问题的时候同时,知道为什么问题会发生以及如何规避掉。

 

参考:

https://blog.csdn.net/wangneng_168/article/details/84599842(Region拆分过程中生成store的名称格式)

https://blog.csdn.net/kirayuan/article/details/19034455(HBase写入太多导致Region过大无法Split问题)