暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

五、HRegion的Split-分区策略

大数据开发笔记 2020-08-15
1232

图片说明

一、介绍

1、每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进
行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑,HMaster 有可能会将某个 Region 转移给其他的 Region Server。

二、Region Split 时机:

当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该 Region 就会进行拆分(0.94 版本之前)。

当 1 个 region 中 的某 个 Store 下所有 StoreFile 的总 大 小超过 Min(R^2 *
"hbase.hregion.memstore.flush.size",hbase.hregion.max.filesize"),该 Region 就会进行拆分,其中 R 为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。

三、Region Split的策略

1、不同版本默认策略

image-20200812201155726

2、HBase-2.x支持7种Region自动拆分Region的策略,类图如下:

image-20200812201820446

其中BusyRegionSplitPolicy是HBase-2.x新增的策略,其他6种在HBase-1.2.x中也可以使用。

3、配置方式

在hbase-site.xml添加,例如

<property> 
    <name>hbase.regionserver.region.split.policy</name>  
    <value>org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy</value> 
</property>

或者在代码中添加

private static Configuration conf = HBaseConfiguration.create();
conf.set("hbase.regionserver.region.split.policy""org.apache.hadoop.hbase.regionserver.SteppingSplitPolicy");

四、不同策略详解
  1. ConstantSizeRegionSplitPolicy

    只要Region的大小达到了hbase.hregion.max.filesize
    所定义的大小,就进行拆分,。阈值设置较大对大表比较友好,但是小表就有可能不会触发分裂,极端情况下可能就1个,这对业务来说并不是什么好事。如果设置较小则对小表友好,但一个大表就会在整个集群产生大量的region,这对于集群的管理、资源使用、failover来说都不是一件好事。

    相关参数

    hbase.hregion.max.filesize
    default: 10737418240 (10GB)
    description: 当一个Region的容量达到这个配置定义的大小后,就会拆分Region

    hbase.server.thread.wakefrequency
    default: 10000 (10s)
    description: 检测Region的大小是否超过限制的时间间隔

    设置方法

    HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf("tableName"));

    tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy");
    // 以下配置根据需要配置或者不配置
    tableDesc.setMaxFileSize(1048576000);

    tableDesc.addFamily(...)
    admin.createTable(tableDesc);

  2. IncreasingToUpperBoundRegionSplitPolicy

    从名字上就可以看出是限制不断增长的文件尺寸的策略。我们以前使用传统关系型数据库的时候或许有这样的经验,有的数据库的文件增长是翻倍增长的,比如第一个文件是64MB,第二个就是128MB,第三个就是256MB。这种策略就是模仿此类情况来实现的。文件尺寸限制是动态的

    相关配置

    hbase.hregion.memstore.flush.size
    default: 134217728 (128MB)
    description: 如果Memstore的大小超过这个字节数,它将被刷新到磁盘.

    hbase.increasing.policy.initial.size
    default: none
    description: IncreasingToUpperBoundRegionSplitPolicy拆分策略下用于计算Region阈值的一个初始值

源码

protected long getSizeToCheck(final int tableRegionsCount) {

    return tableRegionsCount == 0 || tableRegionsCount > 100
               ? getDesiredMaxFileSize()
               : Math.min(getDesiredMaxFileSize(),
                          initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount);
}

如果表目前的Region个数为0或者大于100,那么Region拆分上限值是10G,因为getDesiredMaxFileSize()方法是父类ConstantSizeRegionSplitPolicy的方法,而我们上面分析过,上限大小默认是10G。如果表目前的Region个数在[1,100]之间,公式来确定Region的上限大小:

 protected void configureForRegion(HRegion region) {
        super.configureForRegion(region);
        Configuration conf = this.getConf();
      // 如果配置了'hbase.increasing.policy.initial.size', 取这个值
        this.initialSize = conf.getLong("hbase.increasing.policy.initial.size", -1L);
        if (this.initialSize <= 0L) {
              // 如果设置了MemStoreFlushSize, initialSize的值为该值 * 2
            TableDescriptor desc = region.getTableDescriptor();
            if (desc != null) {
                this.initialSize = 2L * desc.getMemStoreFlushSize();
            }
          // 如果用户没有设置MemStoreFlushSize,配置文件中也没有'hbase.increasing.policy.initial.size'这个配置
          // 那么initialSize = 2 * hbase.hregion.memstore.flush.size的值
            if (this.initialSize <= 0L) {
                this.initialSize = 2L * conf.getLong("hbase.hregion.memstore.flush.size"134217728L);
            }

        }
    }

有以上可知,当一个表只有一个region时

tableRegionsCount = 1
initialSize = 2 * 128M = 256M
getDesiredMaxFileSize() 
10G
min(getDesiredMaxFileSize(), initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount) 
= min(10G, 256M) = 256M

即当第一个Region达到256M的时候开始拆分 

拆分后

tableRegionsCount = 2
initialSize = 2 * 128M = 256M
getDesiredMaxFileSize() 
10G
min(getDesiredMaxFileSize(), initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount) 
= min(10G, 2 * 2 * 2 * 256M) 
= min(10G, 2G)
2G

即当Region大小达到2GB时开始拆分

  • 以此类推,当表有3个Region的时候,Region的最大容量为6.75G

  • 当表有4个Region的时候,计算出来的结果大于10GB,所以使用10GB作为以后的拆分上限

总结一下就是,使用IncreasingToUpperBoundRegionSplitPolicy策略,Region最大容量为: 256M -> 2GB -> 6.75GB -> 10GB -> 10GB -> …

设置方法

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf("tableName"));

tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy");
// 以下配置根据需要配置或者不配置
tableDesc.setValue("hbase.increasing.policy.initial.size""1048576000");
tableDesc.setMaxFileSize(1048576000);
tableDesc.setMemStoreFlushSize(1048576000);

tableDesc.addFamily(...)
admin.createTable(tableDesc);

3.KeyPrefixRegionSplitPolicy

可以自己定义拆分点。KeyPrefixRegionSplitPolicy  是IncreasingToUpperBoundRegionSplitPolicy的子类,在前者的基础上增加了对拆分点(splitPoint,拆分点就是Region被拆分处的rowkey)的定义。它保证了有相同前缀的rowkey不会被拆分到两个不同的Region里面

#前缀长度
keyPrefixRegionSplitPolicy.prefix_length

# 注意,有的博客中这个配置是
# prefix_split_key_policy.prefix_length
# 这个配置在HBase-1.2.x版本中已经标志为 Deprecated

例如

image-20200812213107237

用KeyPrefixRegionSplitPolicy拆分的结果

image-20200812213150538

设置方法

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableNameStr));
tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.KeyPrefixRegionSplitPolicy");
tableDesc.setValue("KeyPrefixRegionSplitPolicy.prefix_length""5");

// 以下配置根据需要配置或者不配置
tableDesc.setValue("hbase.increasing.policy.initial.size""1048576000");
tableDesc.setMaxFileSize(1048576000);
tableDesc.setMemStoreFlushSize(1048576000);

tableDesc.addFamily(...)
admin.createTable(tableDesc);

KeyPrefixRegionSplitPolicy是IncreasingToUpperBoundRegionSplitPolicy类的子类,就是按照rowkey的前缀去拆分Region,但是什么时候拆分,原Region容量的最大值是多少还是需要使用IncreasingToUpperBoundRegionSplitPolicy的方法去计算。SteppingSplitPolicy、DelimitedKeyPrefixRegionSplitPolicy、BusyRegionSplitPolicy (HBase-2.x Only),他们获取Region的拆分阈值的方式都是继承自IncreasingToUpperBoundRegionSplitPolicy。

在这种场景下,按照默认的配置肯定会把同一个前缀的数据切分到不同的Region上。如果你的所有数据都只有一两个前缀,那么KeyPrefixRegionSplitPolicy就无效了,此时采用默认的策略较好。如果你的前缀划分的比较细,你的查询就比较容易发生跨Region查询的情况, 此时采用KeyPrefixRegionSplitPolicy较好。所以这个策略适用的场景是:数据有多种前缀。查询多是针对前缀,比较少跨越多个前缀来查询数据。

4.DelimitedKeyPrefixRegionSplitPolicy

该策略也是继承自IncreasingToUpperBoundRegionSplitPolicy,它也是根据你的rowkey前缀来进行切分的。唯一的不同就是:KeyPrefixRegionSplitPolicy是根据rowkey的固定前几位字符来进行判断,而DelimitedKeyPrefixRegionSplitPolicy是根据分隔符来判断的。在有些系统中rowkey的前缀可能不一定都是定长的,比如你拿服务器的名字来当前缀,有的服务器叫host12有的叫host1。这些场景下严格地要求所有前缀都定长可能比较难,而且这个定长如果未来想改也不容易。DelimitedKeyPrefixRegionSplitPolicy就给了你一个定义长度字符前缀的自由。使用这个策略需要在表定义中加入以下属性

DelimitedKeyPrefixRegionSplitPolicy.delimiter:前缀分隔符,比如你定义了前缀分隔符为_,那么host1_001和host12_999的前缀就分别是host1和host12,使用该参数定义的分隔符分隔rowkey,分隔后的前部分相同的rowkey拆分后一定会在一个Region中。

相关配置

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableNameStr));
tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.DelimitedKeyPrefixRegionSplitPolicy");
tableDesc.setValue("DelimitedKeyPrefixRegionSplitPolicy.delimiter""_");

5.SteppingSplitPolicy

这种策略和IncreasingToUpperBoundRegionSplitPolicy策略很相似,但更简单,第一个Region容量的上限为256M,之后都是10G,这个策略考虑到IncreasingToUpperBoundRegionSplitPolicy会多拆分几个Region(256M -> 2G -> 6.75G -> 10G),所以进行了简化,它的源码只有一个方法,其他都是继承自IncreasingToUpperBoundRegionSplitPolicy类

源码

public class SteppingSplitPolicy extends IncreasingToUpperBoundRegionSplitPolicy {
  /**
   * @return flushSize * 2 if there's exactly one region of the table in question
   * found on this regionserver. Otherwise max file size.
   * This allows a table to spread quickly across servers, while avoiding creating
   * too many regions.
   */

  @Override
  protected long getSizeToCheck(final int tableRegionsCount) {
    return tableRegionsCount == 1  ? this.initialSize : getDesiredMaxFileSize();
  }

设置方法

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableNameStr));
tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.SteppingSplitPolicy"

6.BusyRegionSplitPolicy

此前的拆分策略都没有考虑热点问题。所谓热点问题就是数据库中的Region被访问的频率并不一样,某些Region在短时间内被访问的很频繁,承载了很大的压力,这些Region就是热点Region。BusyRegionSplitPolicy就是为了解决这种场景而产生的

相关配置

hbase.busy.policy.blockedRequests
default: 0.2f
description: 请求阻塞率,即请求被阻塞的严重程度。
取值范围是[0.0, 1.0],默认是0.2,即20%的请求被阻塞的意思。

hbase.busy.policy.minAge
default: 600000 (10min)
description: 拆分最小年龄。
当Region的年龄比这个小的时候不拆分,这是为了防止在判断是否要拆分的时候出现了短时间的访问频率波峰,结果没必要拆分的Region被拆分了,因为短时间的波峰会很快地降回到正常水平。单位毫秒,默认值是600000,即10分钟。

hbase.busy.policy.aggWindow
default: 300000 (5min)
description: 计算是否繁忙的时间窗口,单位毫秒,默认值是300000,即5分钟。
用以控制计算的频率。

计算该Region是否繁忙的计算方法如下:
如果“当前时间–上次检测时间>=hbase.busy.policy.aggWindow”,则进行如下计算:这段时间
被阻塞的请求/这段时间的总请求  =  请求的被阻塞率(aggBlockedRate)
如果“aggBlockedRate  >hbase.busy.policy.blockedRequests”,则判断该Region为繁忙。

如果你的系统常常会出现热点Region,而你对性能有很高的追求,那么这种策略可能会比较适合你。它会通过拆分热点Region来缓解热点
Region的压力,但是根据热点来拆分Region也会带来很多不确定性因素,因为你也不知道下一个被拆分的Region是哪个。

7、DisabledRegionSplitPolicy

这种策略其实不是一种策略。如果你看这个策略的源码会发现就一个方法shouldSplit,并且永远返回false。聪明的你一定一下就猜到了,设置成这种策略就是Region永不自动拆分。如果使用DisabledRegionSplitPolicy让Region永不自动拆分之后,你依然可以通过手动拆分来拆分Region。

public class DisabledRegionSplitPolicy extends RegionSplitPolicy {
  @Override
  protected boolean shouldSplit() {
    return false;
  }
}

这个策略有什么用
无论你设置了哪种拆分策略,一开始数据进入Hbase的时候都只会往一个Region塞数据。必须要等到一个Region的大小膨胀到某个阀值的时候才会根据拆分策略来进行拆分。但是当大量的数据涌入的时候,可能会出现一边拆分一边写入大量数据的情况,由于拆分要占用大量IO,有可能对数据库造成一定的压力。如果你事先就知道这个Table应该按怎样的策略来拆分Region的话,你也可以事先定义拆分点(SplitPoint)。所谓拆分点就是拆分处的rowkey,比如你可以按26个字母来定义25个拆分点,这样数据一到HBase就会被分配到各自所属的Region里面。这时候我们就可以把自动拆分关掉,只用手动拆分。手动拆分有两种情况:预拆分(pre-splitting)和强制拆分(forced splits)。

配置选项

HTableDescriptor tableDesc = new HTableDescriptor(TableName.valueOf(tableNameStr));
tableDesc.setRegionSplitPolicyClassName("org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy");

参考:《hbase不睡觉》


文章转载自大数据开发笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论