天天看点

hbase源码系列(十四)Compact和Split

先上一张图讲一下compaction和split的关系,这样会比较直观一些。

hbase源码系列(十四)Compact和Split

compaction把多个memstore flush出来的storefile合并成一个文件,而split则是把过大的文件split成两个。

之前在delete的时候,我们知道它其实并没有真正删除数据的,那总不能一直不删吧,下面我们就介绍一下它删除数据的过程,它就是compaction。

在讲源码之前,先说一下它的分类和作用。

<b>compaction主要起到如下几个作用:</b>

1)合并文件

2)清除删除、过期、多余版本的数据

3)提高读写数据的效率

<b>minor &amp; major compaction的区别:</b>

1)minor操作只用来做部分文件的合并操作以及包括minversion=0并且设置ttl的过期版本清理,不做任何删除数据、多版本数据的清理工作。

2)major操作是对region下的hstore下的所有storefile执行合并操作,最终的结果是整理合并出一个文件。

先说一下怎么使用吧,下面分别是它们是shell命令,可以在hbase的shell里面执行。

下面我们开始看入口吧,入口在hbaseadmin,找到compact方法,都知道我们compact可以对表操作或者对region进行操作。

1、先把表或者region相关的region信息和server信息全部获取出来

2、循环遍历这些region信息,依次请求compact操作

到这里,客户端的工作就结束了,我们直接到hregionserver找compactregion这个方法吧。

我们先看major compaction吧,直接去看triggermajorcompaction和requestcompaction方法。

compaction

进入方法里面就发现了它把forcemajor置为true就完了,看来这个参数是major和minor的开关,接着看requestcompaction。

上面的步骤是执行selectcompaction创建一个compactioncontext,然后提交compactionrunner。

我们接着看compactioncontext的创建过程吧,这里还需要分是用户创建的compaction和系统创建的compaction。

我们看看这个select的具体实现吧。

这里的select方法,从名字上看是压缩策略的意思,它是由这个参数控制的hbase.hstore.defaultengine.compactionpolicy.class,默认是exploringcompactionpolicy这个类。

接着看exploringcompactionpolicy的selectcompaction方法,发现这个方法是继承来的,找它的父类ratiobasedcompactionpolicy。

从上面可以看出来,major compaction的选择文件几乎没什么限制,只要排除掉正在compacting的文件就行了,反而是minor compact有诸多的排除选项,因为默认的compaction是定时执行的,所以它这方面的考虑吧,排除太大的文件,选择那些过期的文件,排除掉bulkload的文件等等内容。

我们再简单看看applycompactionpolicy这个方法吧,它是minor的时候用的,它的过程就像下图一样。

hbase源码系列(十四)Compact和Split

<b>这个是双层循环: </b>

从0开始,循环n遍(n=文件数),就相当于窗口向右滑动,指针为start

-----&gt;从currentend=start + minfiles(默认是3)-1,每次增加一个文件作为考虑,类似扩张的动作, 窗口扩大, 指针为

--------------&gt;从candidateselection文件里面取出(start, currentend + 1)开始

--------------&gt;小于最小compact数量文件,默认是3,continue

--------------&gt;大于最大compact数量文件,默认是10,continue

--------------&gt;获取这部分文件的大小

--------------&gt;如果这部分文件数量比上次选择方案的文件还小,替换为最小文件方案

--------------&gt;大于memstore flush的大小128m并且符合有一个文件不满这个公式(filesize(i) &lt;= ( 文件总大小- filesize(i) ) * ratio),continue

(注意上面的ratio是干嘛的,这个和前面提到的非高峰时间的数值有关系,非高峰时段这个数值是5,高峰时间段这个值是1.2, 这说明高峰时段不允许compact过大的文件)

--------------&gt;开始判断是不是最优的选择(下面讲的maybestuck是从selectcompaction传入的,可选择的文件超过7个的情况,上面黄色那部分代码)

1)如果maybestuck并且不是初次,如果 文件平均大小 &gt; 上次选择的文件的平均大小*1.05, 替换上次的选择文件方案成为最优解;

2)初次或者不是maybestuck的情况,文件更多的或者文件相同、总文件大小更小的会成为最新的选择文件方案;

如果经过比较之后的最优文件选择方案不为空,就把它返回,否则就把最小文件方案返回。

下面是之前的ratio的参数值,需要配合之前提到的参数配合使用的。

到这里先来个小结吧,从上面可以看得出来,这个minor compaction的文件选择策略就是选小的来,选最多的小文件来合并。

之前的代码我再贴一下,省得大家有点凌乱。

我们去看compactionrunner的run方法吧,它也在当前的类里面。

先是对region进行compact,如果完成了,判断一下优先级,优先级小于等于0,请求系统级别的compaction,否则请求split。

我们还是先看hregion的compact方法,compact开始前,它要先上读锁,不让读了,然后调用hstore中的compact方法。

comact生成新文件的方法很简单,给源文件创建一个storescanner,之前说过storescanner能从多个scanner当中每次都取出最小的kv,然后用storefile.append的方法不停地追加写入即可,这些过程在前面的章节都介绍过了,这里不再重复。

简单的说,就是把这些文件合并到一个文件去了,尼玛,怪不得io那么大。

剩下的就是清理工作了,这里面有意思的就是它会记录一笔日志到writecompactionwalrecord当中,在之间日志恢复那一章的时候,贴出来的代码里面有,只是没有详细的讲。因为走到这里它已经完成了compaction的过程,只是没有把旧的文件移入归档文件当中,它挂掉重启的时候进行恢复干的事情,就是替换文件。

compact完了,要判断一下这个,真是天才啊。

比较方法是这个,blockingfilecount的默认值是7,如果compact之后storefiles的文件数量大于7的话,就很有可能再触发一下,那么major compaction触发的可能性低,minor触发的可能性非常大。

不过没关系,实在选不出文件来,它会退出的。咱们可以将它这个参数hbase.hstore.blockingstorefiles设置得大一些,弄出来一个比较大的数字。

好,我们接着看requestsplit。

先检查一下是否可以进行split,如果可以,把中间的key返回来。

那条件是啥?在这里,if的条件是成立的,条件判断在increasingtoupperboundregionsplitpolicy的shouldsplit方法当中。

<b>遍历region里面所有的store</b>

1、store当中不能有reference文件。

2、store.size &gt; math.min(getdesiredmaxfilesize(), this.flushsize * (tableregionscount * (long)tableregionscount)) 就返回ture,可以split。

getdesiredmaxfilesize()默认是10g,由这个参数来确定hbase.hregion.max.filesize, 当没超过10g的时候它就会根据128mb * (该表在这个rs上的region数量)平方。

midkey怎么找呢?找出最大的hstore,然后通过它来找这个分裂点,最大的文件的中间点。

但是如果是另外一种情况,我们通过客户端来分裂region,我们强制指定的分裂点,这种情况是按照我们设置的分裂点来进行分裂。

分裂点有了,我们接着看,我们发现它又提交了一个splitrequest线程,看run方法。

1、先获得一个tablelock,给这个表上锁

2、执行splittransaction的prepare方法,然后execute

3、结束了释放tablelock

prepare方法当中,主要做了这么件事,new了两个新的region出来:

我们接着看execute方法,这个是重头戏。

<b>总共分三步:</b>

1、创建子region

2、上线子region

3、更改zk当中的状态

我们先看createdaughters

在splitstorefiles这块的,它给每个文件都开一个线程去进行split。

这里其实是给每个文件都创建了reference文件,无论它的文件当中包不包括splitrow。

把引用文件生成在每个子region对应的目录,以便下一步直接重命令目录即可。

重命名目录之后,就是修改meta表了,splitregion的方法是通过put来进行操作的,它修改parent的regioninfo这一列更新为最新的信息,另外又增加了splita和splitb两列,hri_a和hri_b则通过另外两个put插入到meta表当中。

这个过程当中如果出现任何问题,就需要根据journal记录的过程信息进行回滚操作。

到这里split的过程就基本结束了,鉴于compaction和split的对io方面的巨大影响,所以在任何资料里面都是推荐屏蔽自动执行,写脚本在晚上自动进行这些操作。