HBase Compaction

文章目录

  • 1. Compaction
    • 1.1. 基本原理
    • 1.2. Compaction的核心作用
      • 1.2.1. 副作用
    • 1.3. Compaction基本流程
      • 1.3.1. Compaction触发时机
      • 1.3.2. 待合并HFile集合选择策略
        • 1.3.2.1. RatioBasedCompactionPolicy
        • 1.3.2.2. ExploringCompactionPolicy
      • 1.3.3. 挑选合适的执行线程池
      • 1.3.4. HFile文件Compaction执行
    • 1.4. 其他
      • 1.4.1. 为什么只有Major Compaction可以真正删除数据?
      • 1.4.2. 为什么达到TTL的数据可以被Minor Compaction删除?

1. Compaction

  • Compaction是以Store为单位进行的
  • Compaction使读取延迟更加稳定,但是读取时间产生了很大的毛刺
  • Compaction 操作的主要源头来自flush操作
  • 根据待合并HFile文件的选择策略可以得知,正在进行Compaction的HFile文件不会重复被Compaction

1.1. 基本原理

Compaction 是从一个 Region 的一个 Store 中选择部分 HFile 文件进行合并,即Compaction是以Store为单位的。

合并原理是:先从这些待合并的数据文件中依次读出 KeyValue,再由小到大排序后写入一个新的文件。之后,这个新生成的文件就会取代之前已合并的所有文件对外提供服务。

HBase 根据合并规模将 Compaction 分为两类:Minor CompactionMajor Compaction

  • Minor Compaction 是指选取部分小的、相邻的HFile,将它们合并成一个更大的HFile。
    在这个过程中达到TTL的数据会被移除,但是被手动删除的数据不会被移除。这种合并触发频率较高。
  • Major Compaction 是指将一个Store中所有的HFile合并成一个HFile,
    这个过程还会完全清理三类无意义数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。这种合并触发频率较低,默认为7天一次。

1.2. Compaction的核心作用

在HBase的体系架构下,Compaction有以下核心作用:

  • 合并小文件,减少文件数,稳定随机读延迟;
  • 提高数据的本地化率;
  • 清除无效数据,减少数据存储量。

随着HFile文件数不断增多,查询可能需要越来越多的IO操作,读取延迟必然会越来越大。

执行Compaction会使文件个数基本稳定,进而读取IO的次数会比较稳定,延迟就会稳定在一定范围。从图上看,虽然数据读取延迟相比上图稳定了一些,但是读取响应时间有了很大的毛刺,这是因为Compaction在执行的时候占用系统资源导致业务读取性能受到一定波及。

Compaction 的一个重要作用是提高数据的本地化率。本地化率越高,在 HDFS 上访问数据时延迟就越小;相反,本地化率越低,访问数据就可能大概率需要通过网络访问,延迟必然会比较大。
Compaction 合并小文件的同时会将落在远程 DataNode 上的数据读取出来重新写入大文件,合并后的大文件在当前 DataNode 节点上有一个副本,因此可以提高数据的本地化率。极端情况下,Major Compaction 可以将当前 Region 的本地化率提高到100%。这也是最常用的一种提高数据本地化率的方法。

1.2.1. 副作用

Compaction 在执行过程中有个比较明显的副作用:Compaction 操作重写文件会带来很大的带宽压力以及短时间 IO 压力。这点比较容易理解,要将小文件的数据读出来需要 IO,很多小文件数据跨网络传输需要带宽,读出来之后又要写成一个大文件,因为是三副本写入,必然需要网络开销,当然写入 IO 开销也避免不了。因此可以认为,Compaction 就是使用短时间的 IO 消耗以及带宽消耗换取后续查询的低延迟。

1.3. Compaction基本流程

HBase中Compaction只有在特定的触发条件才会执行,一旦触发,HBase会将该Compaction交由一个独立的线程处理,该线程首先会从对应Store中选择合适的HFile文件进行合并,这一步是整个Compaction的核心。选出待合并的文件后,HBase会根据这些HFile文件总大小挑选对应的线程池处理,最后对这些文件执行具体的合并操作

  1. Compaction触发时机
  2. 待合并HFile文件的选择策略
  3. 挑选合适的执行线程
  4. HFile文件合并执行

1.3.1. Compaction触发时机

HBase 中触发 Compaction 的时机有很多,最常见的时机有如下三种:MemStore Flush、后台线程周期性检查以及手动触发。

  • MemStore Flush :应该说 Compaction 操作的源头来自 flush 操作,MemStore Flush 会产生 HFile,文件越来越多就需要执行Compaction。因此在每次执行完 flush 操作之后,都会对当前 Store 中的文件数进行判断,一旦 Store 中总文件数大于 hbase.hstore.compactionThreshold,就会触发 Compaction。需要说明的是,Compaction 都是以 Store 为单位进行的,而在 flush 触发条件下,整个 Region 的所有 Store 都会执行 compact 检查,所以一个 Region 有可能会在短时间内执行多次 Compaction。

参数解释:

  • hbase.hstore.compactionThreshold:默认值为3,如果在任何一个Store中存在超过这个数量的HFile,运行Compaction将所有HFile重写为单个HFile。将该参数设置成较大的值会延迟Compaction,但是当Compaction运行时,则需要更长的时间。
  • 后台线程周期性检查:RegionServer 会在后台启动一个线程 CompactionChecker,定期触发检查对应 Store 是否需要执行 Compaction,检查周期为 hbase.server.thread.wakefrequency * hbase.server.compactchecker.interval.multiplier,大概2小时40分左右执行一次。
    和 MemStore flush 不同的是,该线程优先检查 Store 中总文件数是否大于阈值 hbase.hstore.compactionThreshold,一旦大于阀值就会触发 Compaction;如果不大于阀值,接着检查是否满足 Major Compaction 条件。简单来说,如果当前 Store 中 HFile 的最早更新时间早于某个值 mcTime,就会触发 Major Compaction。
    mcTime 是一个浮动值,浮动区间默认为[7-7×0.5,7+7×0.5],其中7为参数 hbase.hregion.majorcompaction 的值,0.5为参数 hbase.hregion.majorcompaction.jitter 的值,可见默认在7天左右就会执行一次 Major Compaction。

参数解释:

  • hbase.server.thread.wakefrequency:默认值为10000ms,作为服务线程(如日志roller)的睡眠时间间隔。
  • hbase.server.compactchecker.interval.multiplier:默认值为1000,用于决定周期任务频率的值,以确定是否需要Compaction。通常情况下,Compaction是在一些事件(如memstore刷新)之后执行的,但是如果Region在一段时间内没有收到很多写操作,或者由于不同的Compaction策略,可能需要定期检查它。
  • hbase.hregion.majorcompaction:默认值为604800000ms,即7天,表示两次Major Compaction之间的时间间隔。设置为0可以禁用基于时间的自动Major Compaction,但手动触发和基于Store中HFile文件数量的Major Compaction仍将运行。
  • hbase.hregion.majorcompaction.jitter:默认值为0.5,这个值乘以hbase.hregion.majorcompaction使Compaction在给定的时间窗口中以某种随机的时间开始。该参数的值越小,Major Compaction就会越接近hbase.hregion.majorcompaction区间。
  • 手动触发:一般来讲,手动触发 Compaction 大多是为了执行 Major Compaction。
    使用手动触发 Major Compaction 的原因通常有三个,其一,因为很多业务担心自动 Major Compaction 影响读写性能,因此会选择低峰期手动触发;其二,在执行完 alter 操作之后希望立刻生效,手动触发 Major Compaction;其三,HBase 管理员发现硬盘容量不够时手动触发 Major Compaction,删除大量过期数据。

1.3.2. 待合并HFile集合选择策略

选择合适的文件进行合并是整个 Compaction 的核心,因为合并文件的大小及其当前承载的 IO 数直接决定了 Compaction 的效果以及对整个系统其他业务的影响程度。理想的情况是,选择的待合并 HFile 文件集合承载了大量 IO 请求但是文件本身很小,这样Compaction 本身不会消耗太多 IO,而且合并完成之后对读的性能会有显著提升。然而现实中可能大部分 HFile 文件都不会这样。

要选择待合并的HFile文件,首先会对该Store中所有HFile逐一进行排查,排除不满足条件的部分文件,排除条件如下:

  • 排除当前正在执行 Compaction 的文件以及比这些文件更加新的所有文件。
  • 排除某些过大的文件,如果文件大于 hbase.hstore.compaction.max.size,则被排除,否则会产生大量IO消耗。

参数解释:

  • hbase.hstore.compaction.max.size:默认值:Long.MAX_VALUE,以字节表示,大于这个大小的HFile文件将被排除在Compaction之外。当Compaction发生得太频繁时,可以尝试提高这个值。

经过排除后留下来的文件称为候选文件,接下来 HBase 再判断侯选文件是否满足 Major Compaction 条件,如果满足,就会选择全部文件进行合并。判断条件如下所列,只要满足其中一条就会执行 Major Compaction:

  • 用户强制执行 Major Compaction。
  • 满足CompactionChecker条件二,且候选文件数小于 hbase.hstore.compaction.max
  • Store 中含有 reference 文件,reference 文件是 region 分裂产生的临时文件,一般必须在 Compaction 过程中清理。

参数解释:

  • hbase.hstore.compaction.max:默认值为10,执行单次Minor Compaction过程可以被选择的HFile的最大数量,而与符合条件的HFile的数量无关。实际上,该参数的值控制完成一次Compaction所需的时间长度。将其设置得更大意味着Compaction中包含更多的HFile文件。对于大多数情况,默认值是合适的。

如果满足Major Compaction条件,文件选择这一步就结束了,待合并HFile文件就是Store中所有HFile文件。如果不满足Major Compaction条件,就必然为Minor Compaction。

HBase主要有两种Minor Compaction文件选择策略,一种是RatioBasedCompactionPolicy,另一种是 ExploringCompactionPolicy。后者在前者的基础上做了进一步修正。

1.3.2.1. RatioBasedCompactionPolicy

从老到新逐一扫描所有候选文件,满足其中条件之一便停止扫描:

  • 当前文件大小 < 比当前文件新的所有文件大小总和 * ratio。
    其中ratio是一个可变的比例,在高峰期ratio为1.2,受参数hbase.hstore.compaction.ratio控制,非高峰期ratio为5,受参数hbase.hstore.compaction.ratio.offpeak控制,也就是非高峰期允许compact更大的文件。
  • 当前所剩候选文件数 <= hbase.hstore.compaction.min

参数解释:

  • hbase.hstore.compaction.ratio:默认值为1.2F,对于Minor Compaction,这个比率用于确定给定的HFile文件大于等于hbase.hstore.compaction.min.size情况下是否适合进行Compaction。它的作用是限制对较大的HFile的进行Compaction。推荐取值范围为1.0和1.4之间的中等值。对于大多数情况,默认值是合适的。
  • hbase.hstore.compaction.ratio.offpeak:默认值为5.0F,工作原理同hbase.hstore.compaction.ratio参数,但是该值用于非高峰时间段内的Mimor Compaction。默认情况下非高峰时间段是关闭的,因此该参数的值不会生效。
  • hbase.offpeak.start.hour:默认值为-1,非高峰时间的开始,表示为0到23之间的整数。设置为-1禁用非峰值,即默认禁用。
  • hbase.offpeak.end.hour:默认值为-1,非高峰时间的结束,表示为0到23之间的整数。设置为-1禁用非峰值,即默认禁用。
  • hbase.hstore.compaction.min:默认为3,在运行Compaction之前必须符合Compaction条件的HFile文件的最小数量。调优该参数的的目的是避免产生太多需要Compaction的小HFile文件。将该值设置为2将导致每次在一个Store中有两个HFile文件时进行Minor Compaction,这可能不合适。如果您将该值设置得过高,则需要相应地调整所有其他值。对于大多数情况,默认值是合适的。在HBase的早期版本中,该参数被命名为hbase.hstore.compactionThreshold
  • hbase.hstore.compaction.min.size:默认值为134217728,即128M,小于此大小的HFile文件将始终适合进行Minor Compaction。大于等于此大小的HFiles文件由hbase.hstore.compact.ratio评估,以确定它们是否符合条件。

停止扫描后,待合并文件就选择出来了,即当前扫描文件以及比它更新的所有文件。

实际情况下的RatioBasedCompactionPolicy算法效果很差,经常引发大面积的 Minor Compaction,而Minor Compaction过程中不能写入数据,经常因为Compaction而影响IO。

1.3.2.2. ExploringCompactionPolicy

该策略思路基本和 RatioBasedCompactionPolicy 相同,不同的是,Ratio策略在找到一个合适的文件集合之后就停止扫描了,而Exploring策略会记录所有合适的文件集合,并在这些文件集合中寻找最优解。最优解可以理解为:待合并文件数最多或者待合并文件数相同的情况下文件较小,这样有利于减少Compaction带来的IO消耗。

a.修改待合并文件的挑选条件
不再武断地认为,某个文件满足条件就把更加新的文件全部合并进去。确切地说,现在的遍历不强调顺序性了,是把所有的文件都遍历一遍之后每一个文件都去考虑。如果当前文件大小小于最小Compaction大小,则直接进入待合并列表。最小合并大小的配置项:hbase.hstore.compaction.min.size。如果没设定该配置项,则使用hbase.hregion.memstore.flush.size
如果不小于最小Compaction大小,则根据“该文件大小 < (所有文件大小总和 - 该文件大小) * ratio”条件判断符合条件而进入待合并列表的文件。

b.以组合作为计算单元
新的算法不再按文件为单元进行比较了,而是挑出多个文件组合。 挑选组合的条件是:
被挑选的文件必须能通过以上提到的筛选条件,并且组合内含有的文件数必须大于hbase.hstore.compaction.min,小于hbase.hstore.compaction.max
文件太少了没必要合并,还浪费资源;文件太多了太消耗资源,怕机器受不了。
挑选完组合后,比较哪个文件组合包含的文件更多,就合并哪个组合。如果出现平局,就挑选那个文件尺寸总和更小的组合。

1.3.3. 挑选合适的执行线程池

HBase实现中有一个专门的类org.apache.hadoop.hbase.regionserver.CompactSplit负责接收Compaction请求和Split请求,而且为了能够独立处理这些请求,这个类内部构造了三个线程池:longCompactionsshortCompactions以及splitssplits线程池负责处理所有的split请求,longCompactions用来处理大性Compaction,shortCompactions负责处理小型Compaction。

这里需要明确三点:

上述设计目的是能够将请求独立处理,提高系统的处理性能。
大型Compaction并不是Major Compaction,小型Compaction 也并不是MinorCompaction。HBase 定义了一个阈值hbase.regionserver.thread.compaction.throttle,如果Compaction合并的总文件大小超过这个阈值就认为是大型Compaction,否则认为是小型Compaction。大Compaction会分配给longCompactions线程池处理,小Compaction会分配给shortCompactions线程池处理。
longCompactions线程池和shortCompactions线程池默认都只有一个线程,用户可以通过参数hbase.regionserver.thread.compaction.largehbase.regionserver.thread.compaction.small进行配置。

参数解释:

  • hbase.regionserver.thread.compaction.throttle:默认值为2684354560,即2G,Compaction有两个不同的线程池,一个用于大型Compaction,另一个用于小型Compaction。这有助于保持精简表(如hbase:meta)的快速Compaction。如果Compaction大于此阈值,则将进入大型Compaction池。在大多数情况下,默认值是合适的。默认值是2 * hbase.hstore.compaction.max * hbase.hregion.memstore.flush.size

1.3.4. HFile文件Compaction执行

选出待合并的HFile集合,再选出合适的处理线程,接下来执行合并流程。

合并流程主要分为如下几步:

  1. 分别读出待合并 HFile 文件的 KeyValue,进行归并排序处理,之后写到region目录下的.tmp目录下新创建HFile文件中。以下两种数据不会被读取出来:
    (1)如果数据过期了(达到 TTL 所规定的时间),那么这些数据不会被读取出来。
    (2)如果是Major Compaction,那么数据带了墓碑标记也不会被读取出来。

  2. //.tmp目录下新创建的HFile文件移动到对应列族数据目录。

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

  4. 将对应列族数据目录下的Compaction输入文件全部删除。

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

  • 如果 RegionServer 在步骤2之前发生异常,本次 Compaction 会被认定为失败,如果继续进行同样的 Compaction,上次异常对接下来的 Compaction 不会有任何影响,也不会对读写有任何影响。唯一的影响就是多了一份多余的数据。
  • 如果 RegionServer 在步骤2之后、步骤3之前发生异常,同样,仅仅会多一份冗余数据。
  • 如果 RegionServer 在步骤3之后、步骤4之前发生异常,RegionServer 在重新打开 Region 之后首先会从 HLog 中看到标有 Compaction 的日志,因为此时输入文件和输出文件已经持久化到 HDFS,因此只需要根据 HLog 移除 Compaction 输入文件即可。

1.4. 其他

1.4.1. 为什么只有Major Compaction可以真正删除数据?

其实HBase一直拖到Major Compaction的时候才真正把带墓碑标记的数据删掉,并不是因为性能要求,而是之前真的做不到。HBase是建立在HDFS这种只有增加删除而没有修改的文件系统之上的,所以就连用户删除这个动作,在底层都是由新增实现的:

  • 用户增加一条数据就在 HFile 上增加一条 KeyValue,类型是 PUT。
  • 用户删除一条数据还是在 HFile 上增加一条 KeyValue,类型是 DELETE,这就是墓碑标记。
    所以墓碑标记没有什么神秘的,它也就只是另外一个 KeyValue,只不过 value 没有值,而类型是 DELETE。

现在会遇到一个问题:当用户删除数据的时候之前的数据已经被刷写到磁盘上的另外一个HFile了。这种情况很常见,也就是说,墓碑标记和原始数据这两个KeyValue 压根就不在同一个HFile上,如下图所示:
配图8-25

在查询的时候Scan指针其实是把所有的HFile都看过了一遍,它知道了有这条数据,也知道它有墓碑标记,而在返回数据的时候选择不把数据返回给用户,这样在用户的Scan操作看来这条数据就是被删掉了。如果带上RAW=>true参数来Scan,就可以查询到这条被打上墓碑标记的数据。

1.4.2. 为什么达到TTL的数据可以被Minor Compaction删除?

这是因为当数据达到TTL的时候,并不需要额外的一个KeyValue来记录。Compaction时创建的Scan在查询数据的时候,根据“当前时间now - cell的timestamp > TTL”公式来判断cell是否过期。
如果过期了就不返回这条数据。这样当Compaction完成后,过期的数据因为没有被写入新文件,自然就消失了。

你可能感兴趣的:(HBase)