美文网首页
hbase-region split剖析

hbase-region split剖析

作者: Demo_zfs | 来源:发表于2020-05-15 00:47 被阅读0次

        hbase region 切分是hbases水平扩展一个重要因素,将一个region切分为两个小region,并将切分后的region放在不同的节点上,以达到将负载进行均衡到其他节点。下面从split的策略、split流程以及split策略的设置三方面进行讲解region split。

    split策略

        region split的策略分为如下几种DisabledRegionSplitPolicy、ConstantSizeRegionSplitPolicy、IncreasingToUpperBoundRegionSplitPolicy、DelimitedKeyPrefixRegionSplitPolicy 、KeyPrefixRegionSplitPolicy以及SteppingSplitPolicy。本节将从split的触发条件、split切分的切分点以及相应核心源码三个方面进行讲解。

    DisabledRegionSplitPolicy

        禁用split策略,使用该策略,region不会进行自动切分,可以进行人为手动的切分。

    ConstantSizeRegionSplitPolicy

        固定大小自动split策略,在hbase 0.94.0之前使用的默认策略 。

    split触发条件

        当region中存在一个store的大小大于desiredMaxFileSize值则触发split。

        当创建表时指定了MAX_FILESIZE属性,则desiredMaxFileSize = MAX_FILESIZE * (1+浮动率),浮动率由配置hbase.hregion.max.filesize.jitter计算得出。

        当创建表时未指定MAX_FILESIZE属性,则desiredMaxFileSize = hbase.hregion.max.filesize*1+浮动率),浮动率由配置hbase.hregion.max.filesize.jitter计算得出。

        核心触发条件源码如下:

    protected boolean shouldSplit() {

      boolean force = region.shouldForceSplit();

      boolean foundABigStore = false;

    //(1)逐个计算region中的store是否有大于desiredMaxFileSize的。

      for (Store store : region.getStores()) {

        if ((!store.canSplit())) {

          return false;

        }

        if (store.getSize() > desiredMaxFileSize) {

          foundABigStore = true;

        }

      }

      return foundABigStore || force;

    }

    //desiredMaxFileSize的赋值逻辑

    protected void configureForRegion(HRegion region) {

      super.configureForRegion(region);

      Configuration conf = getConf();

    //(1)当表指定了MAX_FILESIZE属性,则desiredMaxFileSize 为该属性的值

      HTableDescriptor desc = region.getTableDesc();

      if (desc != null) {

        this.desiredMaxFileSize = desc.getMaxFileSize();

      }

    //(2) 表未指定MAX_FILESIZE属性,则desiredMaxFileSize = hbase.hregion.max.filesize配置的值

      if (this.desiredMaxFileSize <= 0) {

        this.desiredMaxFileSize = conf.getLong(HConstants.HREGION_MAX_FILESIZE,

          HConstants.DEFAULT_MAX_FILE_SIZE);

      }

    //(3)将desiredMaxFileSize 乘以浮动系数。

      double jitter = conf.getDouble("hbase.hregion.max.filesize.jitter", 0.25D);

      this.jitterRate = (RANDOM.nextFloat() - 0.5D) * jitter;

      long jitterValue = (long) (this.desiredMaxFileSize * this.jitterRate);

      // make sure the long value won't overflow with jitter

      if (this.jitterRate > 0 && jitterValue > (Long.MAX_VALUE - this.desiredMaxFileSize)) {

        this.desiredMaxFileSize = Long.MAX_VALUE;

      } else {

        this.desiredMaxFileSize += jitterValue;

      }

    }

    split触发点计算

        本策略的切分点为该region上最大的store中最大的hfile的中间block的startkey作为splitpoint,如果该splitpoint等于该hfile的startkey或等于endkey则不进行分割。

        核心源码如下:

    protected byte[] getSplitPoint() {

    //(1)是否有指定切分点,指定了切分点则使用指定的切分点进行切分。

      byte[] explicitSplitPoint = this.region.getExplicitSplitPoint();

      if (explicitSplitPoint != null) {

        return explicitSplitPoint;

      }

      List<Store> stores = region.getStores();

      byte[] splitPointFromLargestStore = null;

      long largestStoreSize = 0;

      for (Store s : stores) {

        //(2)获取该store中最大的hfile文件,选取该hfile的midkey作为splitpoint,如果midkey等于该hfile的startkey或等于endkey则返回null。

        byte[] splitPoint = s.getSplitPoint();

        long storeSize = s.getSize();

        if (splitPoint != null && largestStoreSize < storeSize) {

          splitPointFromLargestStore = splitPoint;

          largestStoreSize = storeSize;

        }

      }

      return splitPointFromLargestStore;

    }

    public final byte[] getSplitPoint() throws IOException {

      if (this.storefiles.isEmpty()) {

        return null;

      }

    //(1)获取store上最大storefile,获取该storefile的中间key作为切分点

      return StoreUtils.getLargestFile(this.storefiles).getFileSplitPoint(this.kvComparator);

    }

    byte[] getFileSplitPoint(KVComparator comparator) throws IOException {

      if (this.reader == null) {

        LOG.warn("Storefile " + this + " Reader is null; cannot get split point");

        return null;

      }

    //(1)获取该hfile的中间block的startkey作为midkey,由于并不会遍历block,只是从block的元数据中获取startkey,所以这个过程基本不耗时。

      byte [] midkey = this.reader.midkey();

    //(2)判断获取的midkey是否与该hfile的startkey或endkey相同,相同则返回null即不进行切分。

      if (midkey != null) {

        KeyValue mk = KeyValue.createKeyValueFromKey(midkey, 0, midkey.length);

        byte [] fk = this.reader.getFirstKey();

        KeyValue firstKey = KeyValue.createKeyValueFromKey(fk, 0, fk.length);

        byte [] lk = this.reader.getLastKey();

        KeyValue lastKey = KeyValue.createKeyValueFromKey(lk, 0, lk.length);

        if (comparator.compareRows(mk, firstKey) == 0 || comparator.compareRows(mk, lastKey) == 0) {

          if (LOG.isDebugEnabled()) {

            LOG.debug("cannot split because midkey is the same as first or last row");

          }

          return null;

        }

        return mk.getRow();

      }

      return null;

    }

    IncreasingToUpperBoundRegionSplitPolicy

        动态调整触发切分大小策略,从hbase0.94.0开始默认的自动切分策略,继承自ConstantSizeRegionSplitPolicy。

    split触发条件

    一:指定表在当前region对应的regionserver上的region个数等于0或大于100,则返回desiredMaxFileSize作为触发大小。

    二:指定表在当前region对应的regionserver上的region个数大于0并且小于100,则动态修改split触发的大小,计算公式: min(desiredMaxFileSize,initialSize*tableregioncount*tableregioncount*tableregioncount)

    1)initialSize(初始大小):

    如果配置文件指定了hbase.increasing.policy.initial.size,则使用该值。

    如果配置文件未指定hbase.increasing.policy.initial.size,则使用创建表时指定的MEMSTORE_FLUSHSIZE属性值*2,未指定表的MEMSTORE_FLUSHSIZE属性则使用hbase.hregion.memstore.flush.size*2。

    2)tableregioncount:指定表在当前region对应的regionserver上有多少个region。

    例如:

    前提:

        1.分割之后的子region任然在父region的regionserver上

        2.未指定hbase.increasing.policy.initial.size和表属性MEMSTORE_FLUSHSIZE,指定hbase.hregion.memstore.flush.size为256M。

        3.desiredMaxFileSize为10G

    第一次分割的触发大小:256*2*1*1*1 = 256M

    第二次分割的触发大小:256*2*2*2*2 = 2048M

    第三次分割的触发大小:256*2*4*4*4 =16384M >desiredMaxFileSize(10G),取desiredMaxFileSize(10G)

    之后所有分割的触发大小都是desiredMaxFileSize(10G)。

        核心源码如下:

    protected boolean shouldSplit() {

      boolean force = region.shouldForceSplit();

      boolean foundABigStore = false;

      // (1)获取指定表在当前region对应的regionserver上的region个数

      int tableRegionsCount = getCountOfCommonTableRegions();

      // (2)获取检查大小

      long sizeToCheck = getSizeToCheck(tableRegionsCount);

      for (Store store : region.getStores()) {

        if (!store.canSplit()) {

          return false;

        }

        // Mark if any store is big enough

        long size = store.getSize();

        if (size > sizeToCheck) {

          LOG.debug("ShouldSplit because " + store.getColumnFamilyName() + " size=" + size

                    + ", sizeToCheck=" + sizeToCheck + ", regionsWithCommonTable="

                    + tableRegionsCount);

          foundABigStore = true;

        }

      }

      return foundABigStore | force;

    }

    //计算check size
    protected long getSizeToCheck(final int tableRegionsCount) {

      // (1)region个数等于0或大于100,则返回desiredMaxFileSize作为触发大小

    //  (2)region个数大于0并且小于100,则动态修改split触发的大小,计算公式: min(desiredMaxFileSize,initialSize*tableregioncount*tableregioncount*tableregioncount)

      return tableRegionsCount == 0 || tableRegionsCount > 100

                ? getDesiredMaxFileSize()

                : Math.min(getDesiredMaxFileSize(),

                            initialSize * tableRegionsCount * tableRegionsCount * tableRegionsCount);

    }

    split触发点计算

        和ConstantSizeRegionSplitPolicy策略的split触发点计算一致。

    KeyPrefixRegionSplitPolicy

        根据key的前缀进行切分,继承自IncreasingToUpperBoundRegionSplitPolicy。

    split触发条件

        split触发条件和IncreasingToUpperBoundRegionSplitPolicy一致。

    split触发点计算

        根据rowKey的指定长度的前缀对数据进行分组,以便于将这些数据分到相同的Region中,长度指定由创建表时的KeyPrefixRegionSplitPolicy.prefix_length属性指定(旧版本的属性为:prefix_split_key_policy.prefix_length) 比如rowKey都是8位的,指定前3位是前缀,那么前3位相同的rowKey在进行region split的时候会分到相同的region中。

        核心源码如下:

    protected byte[] getSplitPoint() {

    //(1)调用父类的方法,获取该region上最大的store中最大的hfile的中间block的startkey作为splitpoint。

      byte[] splitPoint = super.getSplitPoint();

    //(2)指定的前缀长度大于0,则获取splitPoint的指定长度作为最后split的切分点。   

      if (prefixLength > 0 && splitPoint != null && splitPoint.length > 0) {

        // group split keys by a prefix

        return Arrays.copyOf(splitPoint,

            Math.min(prefixLength, splitPoint.length));

      } else {

        return splitPoint;

      }

    }

    //获取前缀长度
    protected void configureForRegion(HRegion region) {

      super.configureForRegion(region);

      prefixLength = 0;

    //(1)获取表的KeyPrefixRegionSplitPolicy.prefix_length属性为前缀长度,旧版的为prefix_split_key_policy.prefix_length属性。

      String prefixLengthString = region.getTableDesc().getValue(

          PREFIX_LENGTH_KEY);

      if (prefixLengthString == null) {

        prefixLengthString = region.getTableDesc().getValue(PREFIX_LENGTH_KEY_DEPRECATED);

        if (prefixLengthString == null) {

          return;

        }

      }

      try {

        prefixLength = Integer.parseInt(prefixLengthString);

      } catch (NumberFormatException nfe) {

        return;

      }

    ......

    }

    DelimitedKeyPrefixRegionSplitPolicy

        根据分割符获取前缀进行切分,继承自IncreasingToUpperBoundRegionSplitPolicy。    

    split触发条件

        split触发条件和IncreasingToUpperBoundRegionSplitPolicy一致。

    split触发点计算

        与KeyPrefixRegionSplitPolicy的split触发点计算类似也是使用rowkey的前缀作为splitpoint,不同点在于KeyPrefixRegionSplitPolicy使用固定长度作为前缀,而DelimitedKeyPrefixRegionSplitPolicy指定分隔字符进行拆分作为前缀。分隔字符由表的DelimitedKeyPrefixRegionSplitPolicy.delimiter属性指定。

        核心源码如下:

    protected byte[] getSplitPoint() {

    //(1)调用父类的方法,获取该region上最大的store中最大的hfile的中间block的startkey作为splitpoint。

      byte[] splitPoint = super.getSplitPoint();

    //(2)获取分隔符之前的字符作为splitpoint。

      if (splitPoint != null && delimiter != null) {

        int index = com.google.common.primitives.Bytes.indexOf(splitPoint, delimiter);

        if (index < 0) {

          LOG.warn("Delimiter " + Bytes.toString(delimiter) + "  not found for split key "

              + Bytes.toString(splitPoint));

          return splitPoint;

        }

        return Arrays.copyOf(splitPoint, Math.min(index, splitPoint.length));

      } else {

        return splitPoint;

      }

    }

    //获取分隔符号
    protected void configureForRegion(HRegion region) {

      super.configureForRegion(region);

    //(1)获取表的DelimitedKeyPrefixRegionSplitPolicy.delimiter属性作为分隔符

      String delimiterString = region.getTableDesc().getValue(DELIMITER_KEY);

      if (delimiterString == null || delimiterString.length() == 0) {

        LOG.error(DELIMITER_KEY + " not specified for table " + region.getTableDesc().getTableName() +

          ". Using default RegionSplitPolicy");

        return;

      }

      delimiter = Bytes.toBytes(delimiterString);} 

    SteppingSplitPolicy

        分阶段进行固定大小分割,继承自IncreasingToUpperBoundRegionSplitPolicy。   

    split触发条件

        当只有一个region的时候,使用initialSize作为触发split大小,否则使用desiredMaxFileSize作为触发split大小。initialSize和desiredMaxFileSize都在前面进行过描述。

        核心代码如下:

    protected long getSizeToCheck(final int tableRegionsCount) {

      return tableRegionsCount == 1  ? this.initialSize : getDesiredMaxFileSize();

    }

    split触发点计算

        和ConstantSizeRegionSplitPolicy策略的split触发点计算一致。

    split流程

        当split发生的时候,创建的子region并不会马上把所有的数据写入新的文件,而是创建一个小的链接引用文件指向分割点的头部和尾部。这些引用文件会在compactions操作逐渐被清除。只有当region没有引用文件的时候才可以进行split操作。

        regionserver在split开始和结束都会通知hmaster去更新.META表,使客户端可以知道新的子region,重新组织hdfs上的文件路径。

    split操作的流程如下图所示:

    split流程

    1.在zookeeper的/hbase/region-in-transition/region-name路径下创建znode并标记状态为SPLITTING.。

    2.hmaster监听/hbase/region-in-transition/region-name路径得知该region正在进行split

    3.regionserver在hdfs的父region路径下创建.splits路径

    4.regionserver上关闭父region,此时父region为offline,当有客户端访问该父region时会报NotServingRegionException错误。

    5.在hdfs的.splits路径下创建子region A、B的路径,然后split,其实就是在子region A、B的路径下创建引用文件指向父region的文件。

    6.创建实际的子region路径(上面创建的文件都是在父region路径下),并把引用文件移动到该路径下。

    7.该regionserver向拥有.META表的regionserver发送一条put请求,修改该spliting region的状态offline,并且添加子region的regionname。在这个时候并没有单独的子region信息,当客户端scan表.META时知道到父region在split,但是不知道子region的信息。当put请求成功后父region会进行快速的split。

    8.该regionserver并发的打开两个子region。

    9.该regionserver将两个子region的信息(host)发送到拥有.META表的regionserver,添加到.META表中。这时两个子region上线,客户端可以知道这两个子region并向这两个子region发送请求。客户端会缓存.META表中的数据,当使用缓存中的数据进行访问regionserver时出现问题,客户端会重新请求.META表中的内容进行缓存。

    10.将步骤1创建的znode,将该状态转为split,这时split操作完成,hmaster得知split操作完成。

    11.完成上述步骤后,hdfs仍然包含引用文件指向父region,这些引用文件会在子region进行compactions时进行移除。hmaster中的gc任务会周期的检查子region是否还有引用父region的文件,没有的话会将父region进行移除。

    split策略设置

        分为两种方式进行设置

    1)全局方式,通过修改配置文件中的hbase.regionserver.region.split.policy属性进行指定策略,未指定策略的表都使用该配置指定的策略。

    2)表级别,通过指定表的属性进行指定split策略,hbase shell中案例如下:

    create 'test1', { NAME => 'cf',COMPRESSION => 'GZ'}, {METADATA => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy',MAX_FILESIZE=> '10737418240'}}

        今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。

    相关文章

      网友评论

          本文标题:hbase-region split剖析

          本文链接:https://www.haomeiwen.com/subject/klufohtx.html