hbase split 源碼分析之split策略

  在工做中接觸到split,因而查看了這塊的源代碼,先看到了split的策略,今天就說說這個吧,後續還會有split的其餘源碼分析和compact相關的源碼分析。java

  看了不少其餘人的博客,不少都是轉發的,原創的也都沒有註明是哪一個版本。其實給不少讀者形成混淆,我這裏是基於Hbase-0.98.13 版本做爲分析的,注意:不一樣版本的此部分源碼極可能不同。apache

  在這個版本中使用的split策略是IncreasingToUpperBoundRegionSplitPolicy。確切來講他是0.94版本之後的策略。類爲org/apache/hadoop/hbase/regionserver/IncreasingToUpperBoundRegionSplitPolicy.java 。首先看一下 configureForRegion 方法,其中的initialSize 在之後會用到。這個方法其實主要目的也就是在初始化initialSizeide

@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。oop

@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方法看看this

long getDesiredMaxFileSize() {
    return desiredMaxFileSize;
}

  這個方法是ConstantSizeRegionSplitPolicy中的方法,別以爲奇怪,由於IncreasingToUpperBoundRegionSplitPolicy extends ConstantSizeRegionSplitPolicy,這個找不到線索就看看這個類,而後找到了以下代碼debug

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,這種說法我暫時不明白,先放一放。code

  回到shouldSplit方法中,咱們看看canSplit方法作了什麼?server

@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的判斷了。
好的,來總結一下:ip

hbase對一個region切分,有幾個條件:
一、若是是用戶請求切分,則無論什麼狀況均可以切分。
二、若是非用戶請求,而且這個region中任意store含有引用文件,則不切分
三、若是不是用戶請求,也沒有引用文件,則判斷每一個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的發生。

  這一篇先分享到這。

相關文章
相關標籤/搜索