在工作中接触到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*2 = 128M *2 =256M时 才会允许split,如果达到这个值切分后,会有两个region,其中一个region中的某个store大小大于 2*2*2*flushsize*2 = 2048M 时,则允许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的发生。