hbase split 源码分析之split策略

Awara 2019-06-27

在工作中接触到split,于是查看了这块的源代码,先看到了split的策略,今天就说说这个吧,后续还会有split的其他源码分析和compact相关的源码分析。

看了很多其他人的博客,很多都是转发的,原创的也都没有注明是哪个版本。其实给很多读者造成混淆,我这里是基于Hbase-0.98.13 版本作为分析的,注意:不同版本的此部分源码很可能不一样。

在这个版本中使用的split策略是IncreasingToUpperBoundRegionSplitPolicy。确切来说他是0.94版本以后的策略。类为org/apache/hadoop/hbase/regionserver/IncreasingToUpperBoundRegionSplitPolicy.java 。首先看一下 configureForRegion 方法,其中的initialSize 在以后会用到。这个方法其实主要目的也就是在初始化initialSize

@Override
  protected void configureForRegion(HRegion region) {
    super.configureForRegion(region);
    Configuration conf = getConf();
    //如果设置了hbase.increasing.policy.initial.size,则使用用户设置的
    this.initialSize = conf.getLong("hbase.increasing.policy.initial.size", -1);
    if (this.initialSize > 0) {
      return;
    }
    //如果没有设置,看hbase.hregion.memstore.flush.size有没有
    //如果设置了则initialSize=2*hbase.hregion.memstore.flush.size,
    //如果没有则使用默认的1024*1024*128L  (128M)
    HTableDescriptor desc = region.getTableDesc();
    if (desc != null) {
      this.initialSize = 2*desc.getMemStoreFlushSize();
    }
    if (this.initialSize <= 0) {
      this.initialSize = 2*conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE,
        HTableDescriptor.DEFAULT_MEMSTORE_FLUSH_SIZE);
    }
  }

如果配置默认,这个方法将initialSize 初始化为2*hbase.hregion.memstore.flush.size
再来看看其他的方法,有一个方法叫shouldSplit,顾名思义就是判断能不能split。

@Override
protected boolean shouldSplit() {
    if (region.shouldForceSplit())
        return true;
    boolean foundABigStore = false;
    // 得到同张表的在线region个数
    // Get count of regions that have the same common table as this.region
    int tableRegionsCount = getCountOfCommonTableRegions();
    // 得到分割的阀值
    // Get size to check
    long sizeToCheck = getSizeToCheck(tableRegionsCount);
    // 检查每一个store,如果有不能split的则此次判断为false
    for (Store store : region.getStores().values()) {
        // If any of the stores is unable to split (eg they contain
        // reference files)
        // then don't split
        // 如果当前region不能分割,则返回false
        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;
}

其中long sizeToCheck = getSizeToCheck(tableRegionsCount);这句很重要,跟进去查看

protected long getSizeToCheck(final int tableRegionsCount) {
    // safety check for 100 to avoid numerical overflow in extreme cases
    return tableRegionsCount == 0 || tableRegionsCount > 100 ? getDesiredMaxFileSize()
            : Math.min(getDesiredMaxFileSize(), this.initialSize
                    * tableRegionsCount * tableRegionsCount
                    * tableRegionsCount);
}

这是一个三目运算,如果这个table中在线的region个数为0或则大于100,则使用getDesiredMaxFileSize()方法得到这个阀值,否则就使用getDesiredMaxFileSize()得到的阀值和initialSize * (tableRegionsCount的三次方)中小的那一个,在跟进去getDesiredMaxFileSize方法看看

long getDesiredMaxFileSize() {
    return desiredMaxFileSize;
}

这个方法是ConstantSizeRegionSplitPolicy中的方法,别觉得奇怪,因为IncreasingToUpperBoundRegionSplitPolicy extends ConstantSizeRegionSplitPolicy,这个找不到线索就看看这个类,然后找到了如下代码

private long desiredMaxFileSize;

  @Override
  protected void configureForRegion(HRegion region) {
super.configureForRegion(region);
Configuration conf = getConf();
HTableDescriptor desc = region.getTableDesc();
if (desc != null) {
  this.desiredMaxFileSize = desc.getMaxFileSize();
}
//设置desiredMaxFileSize = hbase.hregion.max.filesize的大小默认是10G
if (this.desiredMaxFileSize <= 0) {
  this.desiredMaxFileSize = conf.getLong(HConstants.HREGION_MAX_FILESIZE,
    HConstants.DEFAULT_MAX_FILE_SIZE);
}
//如果设置了hbase.hregion.max.filesize.jitter  则desiredMaxFileSize做个抖动
float jitter = conf.getFloat("hbase.hregion.max.filesize.jitter", Float.NaN);
if (!Float.isNaN(jitter)) {
  this.desiredMaxFileSize += (long)(desiredMaxFileSize * (RANDOM.nextFloat() - 0.5D) * jitter);
}
  }

原来如果设置了hbase.hregion.max.filesize.jitter,则用HREGION_MAX_FILESIZE + HREGION_MAX_FILESIZE×随机小数×hbase.hregion.max.filesize.jitter,其中jitter默认为0.5,HREGION_MAX_FILESIZE 其实就是hbase.hregion.max.filesize,默认是10G,至于为什么抖动,有的人说是为了防止重启regionServer时进行大量的major compact,这种说法我暂时不明白,先放一放。

回到shouldSplit方法中,我们看看canSplit方法做了什么?

@Override
public boolean canSplit() {
    this.lock.readLock().lock();
    try {
        // Not split-able if we find a reference store file present in the
        // store.
        boolean result = !hasReferences();
        if (!result && LOG.isDebugEnabled()) {
            LOG.debug("Cannot split region due to reference files being there");
        }
        return result;
    } finally {
        this.lock.readLock().unlock();
    }
}

很简单,就是看看有没有引用文件,如果有则不能split,如果没有则可以,再次回到shouldSplit方法,可以看到如果当前的store的大小大于刚刚计算出的阀值,则返回true,算是通过split的判断了。
好的,来总结一下:

hbase对一个region切分,有几个条件:
1、如果是用户请求切分,则不管什么情况都可以切分。
2、如果非用户请求,并且这个region中任意store含有引用文件,则不切分
3、如果不是用户请求,也没有引用文件,则判断每个store的大小,只要其中有一个大于阀值,则切分。这个阀值在上面已经有说到。

总结


  说下这个策略的含义。0.94版本之前使用的是ConstantSizeRegionSplitPolicy策略,此策略只是大于一个基本固定的阀值就允许split,而现在的策略则是store大小大于一个变化的阀值就允许split,什么意思呢,举个例子,当hbase相关split的属性都没有配置,采用默认,一张表刚建立,默认情况只有1个region,那么逻辑上是当这个region的store大小超过 1×1×1×flushsize = 128M(没有自己设置flushSize)时 才会允许split,如果达到这个值切分后,会有两个region,其中一个region中的某个store大小大于 2×2×2×flushsize = 512M 时,则允许split,如此计算下去,直到这个大小超过了hbase.hregion.max.filesize+ hbase.hregion.max.filesize随机小数hbase.hregion.max.filesize.jitter才允许split,基本也就固定了,如果粗劣的计算可以把这个hbase.hregion.max.filesize的大小作为最后的阀值,默认是10G,也就说当这个阀值变化到10G,这个阀值就基本上不再变化。

这种思想使得阀值达到一个基本固定的值之前先做了几次split,而这几次split的数据量很少,对hbase的影响也没有那么大,而且相当于数据导入量不大的时候就做了一次“预分region”,在一定意义上减少了以后的热点region的发生。

这一篇先分享到这。

相关推荐