hbase Compaction

Compaction会从一个region的一个store中选择一些hfile文件进行合并。合并说来原理很简单,先从这些待合并的数据文件中读出KeyValues,再按照由小到大排列后写入一个新的文件中。以后,这个新生成的文件就会取代以前待合并的全部文件对外提供服务。HBase根据合并规模将Compaction分为了两类:MinorCompaction和MajorCompaction算法

  • Minor Compaction是指选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile,在这个过程当中不会处理已经Deleted或Expired的Cell。一次Minor Compaction的结果是更少而且更大的StoreFile。
  • Major Compaction是指将全部的StoreFile合并成一个StoreFile,这个过程还会清理三类无心义数据:被删除的数据、TTL过时数据、版本号超过设定版本号的数据。另外,通常状况下,Major Compaction时间会持续比较长,整个过程会消耗大量系统资源,对上层业务有比较大的影响。所以线上业务都会将关闭自动触发Major Compaction功能,改成手动在业务低峰期触发。

 

Compaction做用 | 反作用

上文提到,随着hfile文件数不断增多,一次查询就可能会须要愈来愈多的IO操做,延迟必然会愈来愈大,以下图一所示,随着数据写入不断增长,文件数不断增多,读取延时也在不断变大。而执行compaction会使得文件数基本稳定,进而IO Seek次数会比较稳定,延迟就会稳定在必定范围。然而,compaction操做重写文件会带来很大的带宽压力以及短期IO压力。所以能够认为,Compaction就是使用短期的IO消耗以及带宽消耗换取后续查询的低延迟。从图上来看,就是延迟有很大的毛刺,但整体趋势基本稳定不变,见下图二。性能

为了换取后续查询的低延迟,除了短期的读放大以外,Compaction对写入也会有很大的影响。 咱们首先假设一个现象:当写请求很是多,致使不断生成HFile,但compact的速度远远跟不上HFile生成的速度,这样就会使HFile的数量会愈来愈多,致使读性能急剧降低。为了不这种状况,在HFile的数量过多的时候会限制写请求的速度:在每次执行MemStore flush的操做前,若是HStore的HFile数超过hbase.hstore.blockingStoreFiles (默认7),则会阻塞flush操做hbase.hstore.blockingWaitTime时间,在这段时间内,若是compact操做使得HStore文件数降低到回这个值,则中止阻塞。另外阻塞超过期间后,也会恢复执行flush操做。这样作就能够有效地控制大量写请求的速度,但同时这也是影响写请求速度的主要缘由之一。.net

可见,Compaction会使得数据读取延迟一直比较平稳,但付出的代价是大量的读延迟毛刺和必定的写阻塞。线程

Compaction流程

了解了必定的背景知识后,接下来须要从全局角度对Compaction进行了解。整个Compaction始于特定的触发条件,好比flush操做、周期性地Compaction检查操做等。一旦触发,HBase会将该Compaction交由一个独立的线程处理,该线程首先会从对应store中选择合适的hfile文件进行合并,这一步是整个Compaction的核心,选取文件须要遵循不少条件,好比文件数不能太多、不能太少、文件大小不能太大等等,最理想的状况是,选取那些承载IO负载重、文件小的文件集,实际实现中,HBase提供了多个文件选取算法:RatioBasedCompactionPolicy、ExploringCompactionPolicy和StripeCompactionPolicy等,用户也能够经过特定接口实现本身的Compaction算法;选出待合并的文件后,HBase会根据这些hfile文件总大小挑选对应的线程池处理,最后对这些文件执行具体的合并操做。设计

能够经过下图简单地梳理上述流程:日志

 

 

触发时机server

HBase中能够触发compaction的因素有不少,最多见的因素有这么三种:Memstore Flush、后台线程周期性检查、手动触发。blog

1. Memstore Flush: 应该说compaction操做的源头就来自flush操做,memstore flush会产生HFile文件,文件愈来愈多就须要compact。所以在每次执行完Flush操做以后,都会对当前Store中的文件数进行判断,一旦文件数# > ,就会触发compaction。须要说明的是,compaction都是以Store为单位进行的,而在Flush触发条件下,整个Region的全部Store都会执行compact,因此会在短期内执行屡次compaction。接口

2. 后台线程周期性检查: 后台线程CompactionChecker按期触发检查是否须要执行compaction,检查周期为:hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier。和flush不一样的是,该线程优先检查文件数#是否大于,一旦大于就会触发compaction。若是不知足,它会接着检查是否知足major compaction条件,简单来讲,若是当前store中hfile的最先更新时间早于某个值mcTime,就会触发major compaction,HBase预想经过这种机制按期删除过时数据。上文mcTime是一个浮动值,浮动区间默认为[7-7*0.2,7+7*0.2],其中7为hbase.hregion.majorcompaction,0.2为hbase.hregion.majorcompaction.jitter,可见默认在7天左右就会执行一次major compaction。用户若是想禁用major compaction,只须要将参数hbase.hregion.majorcompaction设为0ip

3. 手动触发:通常来说,手动触发compaction一般是为了执行major compaction,缘由有三,其一是由于不少业务担忧自动major compaction影响读写性能,所以会选择低峰期手动触发;其二也有多是用户在执行完alter操做以后但愿马上生效,执行手动触发major compaction;其三是HBase管理员发现硬盘容量不够的状况下手动触发major compaction删除大量过时数据;不管哪一种触发动机,一旦手动触发,HBase会不作不少自动化检查,直接执行合并。

 

 

挑选合适的线程池

HBase实现中有一个专门的线程CompactSplitThead负责接收compact请求以及split请求,并且为了可以独立处理这些请求,这个线程内部构造了多个线程池:largeCompactions、smallCompactions以及splits等,其中splits线程池负责处理全部的split请求,largeCompactions和smallCompaction负责处理全部的compaction请求,其中前者用来处理大规模compaction,后者处理小规模compaction。这里须要明白三点:

1. 上述设计目的是为了可以将请求独立处理,提供系统的处理性能。

2. 哪些compaction应该分配给largeCompactions处理,哪些应该分配给smallCompactions处理?是否是Major Compaction就应该交给largeCompactions线程池处理?不对。这里有个分配原则:待compact的文件总大小若是大于值throttlePoint(能够经过参数 hbase.hregion.majorcompaction配置, 默认为2.5G),分配给largeCompactions处理,不然分配给smallCompactions处理。

3. largeCompactions线程池和smallCompactions线程池默认都只有一个线程,用户能够经过参数 hbase.regionserver.thread.compaction.large和hbase.regionserver.thread.compaction.small进行配置

 

 

选择合适HFile合并

选择合适的文件进行合并是整个compaction的核心,由于合并文件的大小以及其当前承载的IO数直接决定了compaction的效果。最理想的状况是,这些文件承载了大量IO请求可是大小很小,这样compaction自己不会消耗太多IO,并且合并完成以后对读的性能会有显著提高。然而现实状况可能大部分都不会是这样,在0.96版本和0.98版本,分别提出了两种选择策略,在充分考虑总体状况的基础上选择最佳方案。不管哪一种选择策略,都会首先对该Store中全部HFile进行一一排查,排除不知足条件的部分文件:

1. 排除当前正在执行compact的文件及其比这些文件更新的全部文件(SequenceId更大)

2. 排除某些过大的单个文件,若是文件大小大于hbase.hzstore.compaction.max.size( 默认Long最大值 ),则被排除,不然会产生大量IO消耗

通过排除的文件称为候选文件,HBase接下来会再判断是否知足major compaction条件,若是知足,就会选择所有文件进行合并。判断条件有下面三条,只要知足其中一条就会执行major compaction:

1. 用户强制执行major compaction

2.  长时间没有进行compact(CompactionChecker的判断条件2)且候选文件数小于hbase.hstore.compaction.max(默认10)

3. Store中含有Reference文件,Reference文件是split region产生的临时文件,只是简单的引用文件,通常必须在compact过程当中删除

若是不知足major compaction条件,就必然为minor compaction,HBase主要有两种minor策略:RatioBasedCompactionPolicy和ExploringCompactionPolicy,下面分别进行介绍:

RatioBasedCompactionPolicy

从老到新逐一扫描全部候选文件,知足其中条件之一便中止扫描:

(1)当前文件大小 < 比它更新的全部文件大小总和 * ratio,其中ratio是一个可变的比例,在高峰期时ratio为1.2,非高峰期为5,也就是非高峰期容许compact更大的文件。那何时是高峰期,何时是非高峰期呢?用户能够配置参数hbase.offpeak.start.hour和hbase.offpeak.end.hour来设置高峰期

(2)当前所剩候选文件数 <= hbase.store.compaction.min(默认为3)

中止扫描后,待合并文件就选择出来了,即为当前扫描文件+比它更新的全部文件

ExploringCompactionPolicy

该策略思路基本和RatioBasedCompactionPolicy相同,不一样的是,Ratio策略在找到一个合适的文件集合以后就中止扫描了,而Exploring策略会记录下全部合适的文件集合,并在这些文件集合中寻找最优解。最优解能够理解为:待合并文件数最多或者待合并文件数相同的状况下文件大小较小,这样有利于减小compaction带来的IO消耗。具体流程戳 这里

须要注意的是,Ratio策略是0.94版本的默认策略,而0.96版本以后默认策略就换为了Exploring策略,在cloudera博文 《what-are-hbase-compactions》 中,做者给出了一个二者的简单性能对比,基本能够看出后者在节省IO方面会有10%左右的提高:

 

 

 

执行HFile文件合并

上文一方面选出了待合并的HFile集合,一方面也选出来了合适的处理线程,万事俱备,只欠最后真正的合并。合并流程提及来也简单,主要分为以下几步:

1. 分别读出待合并hfile文件的KV,并顺序写到位于./tmp目录下的临时文件中

2. 将临时文件移动到对应region的数据目录

3. 将compaction的输入文件路径和输出文件路径封装为KV写入WAL日志,并打上compaction标记,最后强制执行sync

4. 将对应region数据目录下的compaction输入文件所有删除

上述四个步骤看起来简单,但实际是很严谨的,具备很强的容错性和完美的幂等性:

1. 若是RS在步骤2以前发生异常,本次compaction会被认为失败,若是继续进行一样的compaction,上次异常对接下来的compaction不会有任何影响,也不会对读写有任何影响。惟一的影响就是多了一份多余的数据。

2. 若是RS在步骤2以后、步骤3以前发生异常,一样的,仅仅会多一份冗余数据。

3. 若是在步骤3以后、步骤4以前发生异常,RS在从新打开region以后首先会从WAL中看到标有compaction的日志,由于此时输入文件和输出文件已经持久化到HDFS,所以只须要根据WAL移除掉compaction输入文件便可

相关文章
相关标签/搜索