Ch12-HBase 之 Region Split

Ch12-HBase 之 Region Split

March 24, 2021
Apache HBase
hbase

HBase Region Split 整个过程可以分为如下几步,触发 Region Split寻找 SplitPoint拆分 Region等待 major compaction 删除旧的 Region

1. 触发 Region Split #

触发 Region Split 有好几种策略,比如除了 3 种默认过的策略,还有 DelimitedKeyPrefixRegionSplitPolicyKeyPrefixRegionSplitPolicyDisableSplitPolicy 等策略,这里只介绍 3 种默认的策略。分别是 ConstantSizeRegionSplitPolicy 策略、IncreasingToUpperBoundRegionSplitPolicy 策略和 SteppingSplitPolicy 策略。

1.1 ConstantSizeRegionSplitPolicy #

ConstantSizeRegionSplitPolicy 策略是 0.94 版本之前的默认拆分策略,这个策略的拆分规则是:当 region 大小达到 hbase.hregion.max.filesize(默认 10G)后拆分。 这种拆分策略对于小表不太友好,按照默认的设置,如果 1 个表的 Hfile 小于 10G 就一直不会拆分。注意 10G 是压缩后的大小,如果使用了压缩的话。

如果 1 个表一直不拆分,访问量小也不会有问题,但是如果这个表访问量比较大的话,就比较容易出现性能问题。这个时候只能手工进行拆分。还是很不方便。

1.2 IncreasingToUpperBoundRegionSplitPolicy #

这种切分策略微微有些复杂,总体来看和 ConstantSizeRegionSplitPolicy 思路相同,一个 region 中最大 store 大小大于设置阈值就会触发切分。但是这个阈值并不像 ConstantSizeRegionSplitPolicy 是一个固定的值,而是会在一定条件下不断调整,调整规则和 region 所属表在当前 regionserver 上的 region 个数有关系:(#regions) * (#regions) * (#regions) * flush size * 2,当然阈值并不会无限增大,最大值为用户设置的 MaxRegionFileSize。

从这个算是我们可以得出 flushsize 为 128M、maxFileSize 为 10G 的情况下,可以计算出 Region 的分裂情况如下:

第一次拆分大小为:min(10G,11128M)=128M
第二次拆分大小为:min(10G,33128M)=1152M
第三次拆分大小为:min(10G,55128M)=3200M
第四次拆分大小为:min(10G,77128M)=6272M
第五次拆分大小为:min(10G,99128M)=10G
第五次拆分大小为:min(10G,1111128M)=10G

1.3 SteppingSplitPolicy #

SteppingSplitPolicy 是在 Hbase 2.0 版本后的默认策略,,拆分规则为:If region=1 then: flush size * 2 else: MaxRegionFileSize

还是以 flushsize 为 128M、maxFileSize 为 10 场景为列,计算出 Region 的分裂情况如下:

第一次拆分大小为:2*128M=256M
第二次拆分大小为:10G

从上面的计算我们可以看出,这种策略兼顾了 ConstantSizeRegionSplitPolicy 策略和 IncreasingToUpperBoundRegionSplitPolicy 策略,对于小表也肯呢个比较好的适配。

从上面的计算我们可以看到这种策略能够自适应大表和小表,但是这种策略会导致小表产生比较多的小 region,对于小表还是不是很完美。

2. 寻找 SplitPoint #

SplitPoint 规定为整个 region 中最大 store 中的最大文件中最中心的一个 block 的首个 rowkey。如果定位到的 rowkey 是整个文件的首个 rowkey 或者最后一个 rowkey 的话,就认为没有切分点。

3. 拆分 Region #

hbase-split

从上图我们可以看出 Region 切分的详细流程如下:

  1. 会 ZK 的 /hbase/region-in-transition/region-name 下创建一个 znode,并设置状态为 SPLITTING
  2. master 通过 watch 节点检测到 Region 状态的变化,并修改内存中 Region 状态的变化
  3. RegionServer 在父 Region 的目录下创建一个名称为 .splits 的子目录
  4. RegionServer 关闭父 Region,强制将数据刷新到磁盘,并这个 Region 标记为 offline 的状态。此时,落到这个 Region 的请求都会返回 NotServingRegionException 这个错误
  5. RegionServer 在 .splits 创建 daughterA 和 daughterB,并在文件夹中创建对应的 reference 文件,指向父 Region 的 Region 文件
  6. RegionServer 在 HDFS 中创建 daughterA 和 daughterB 的 Region 目录,并将 reference 文件移动到对应的 Region 目录中
  7. 在 hbase:meta 表中设置父 Region 为 offline 状态,不再提供服务,并将父 Region 的 daughterA 和 daughterB 的 Region 添加到 hbase:meta 表中,已表名父 Region 被拆分成了 daughterA 和 daughterB 两个 Region
  8. RegionServer 并行开启两个子 Region,并正式提供对外写服务
  9. RegionSever 将 daughterA 和 daughterB 添加到 hbase:meta 表中,这样就可以从 hbase:meta 找到子 Region,并可以对子 Region 进行访问了
  10. RegionServr 修改 /hbase/region-in-transition/region-name 的 znode 的状态为 SPLIT

备注:

为了减少对业务的影响,Region 的拆分并不涉及到数据迁移的操作,而只是创建了对父 Region 的指向。只有在做大合并的时候,才会将数据进行迁移。

4. 其他问题 #

4.1 reference 文件查找流程 #

hbase-split-reference

  1. 根据文件名来判断是否是 reference 文件
  2. 由于 reference 文件的命名规则为前半部分为父 Region 对应的 File 的文件名,后半部分是父 Region 的名称,因此读取的时候也根据前半部分和后半部分来识别
  3. 根据 reference 文件的内容来确定扫描的范围,reference 的内容包含两部分,一部分是切分点 splitkey,另一部分是 boolean 类型的变量(true 或者 false)。如果为 true 则扫描文件的上半部分,false 则扫描文件的下半部分
  4. 接下来确定了扫描的文件,以及文件的扫描范围,那就按照正常的文件检索了

4.2 父 region 的数据什么时候会迁移到子 region 目录? #

子 region 发生 major_compaction 时。我们知道 compaction 的执行实际上是将 store 中所有小文件一个 KV 一个 KV 从小到大读出来之后再顺序写入一个大文件,完成之后再将小文件删掉,因此 compaction 本身就需要读取并写入大量数据。子 region 执行 major_compaction 后会将父目录中属于该子 region 的所有数据读出来并写入子 region 目录数据文件中。可见将数据迁移放到 compaction 这个阶段来做,是一件顺便的事。

4.3 父 region 什么时候会被删除? #

实际上 HMaster 会启动一个线程定期遍历检查所有处于 splitting 状态的父 region,确定检查父 region 是否可以被清理。检测线程首先会在 meta 表中揪出所有 split 列为 true 的 region,并加载出其分裂后生成的两个子 region(meta 表中 splitA 列和 splitB 列),只需要检查此两个子 region 是否还存在引用文件,如果都不存在引用文件就可以认为该父 region 对应的文件可以被删除。

4.4 Region 切分事务性如何保证? #

HBase 2.0 之前基于状态机保证(未完全保证,仍可能会存在大量 RIT),HBase 2.0 之后基于内部分布式事务框架 Procedure V2(HBASE-12439) 保证。

region-meta

5. 参考文献 #