HBase MemStore和Compaction剖析

1.概述

  客户端读写数据是先从Zookeeper中获取RegionServer的元数据信息,好比Region地址信息。在执行数据写操做时,HBase会先写MemStore,为何会写到MemStore。本篇博客将为读者剖析HBase MemStore和Compaction的详细内容。shell

2.内容

  HBase的内部通讯和数据交互是经过RPC来实现,关于HBase的RPC实现机制下篇博客为你们分享。客户端应用程序经过RPC调用HBase服务端的写入、删除、读取等请求,由HBase的Master分配对应的RegionServer进行处理,获取每一个RegionServer中的Region地址,写入到HFile文件中,最终进行数据持久化。网络

  在了解HBase MemStore以前,咱们能够先来看看RegionServer的体系结构,其结构图以下所示: 分布式

  在HBase存储中,虽然Region是分布式存储的最小单元,单并非存储的最小单元。从图中可知,事实上Region是由一个或者多个Store构成的,每一个Store保存一个列族(Columns Family)。而每一个Store又由一个MemStore和0到多个StoreFile构成,而StoreFile以HFile的格式最终保存在HDFS上。性能

2.1 写入流程

  HBase为了保证数据的随机读取性能,在HFile中存储RowKey时,按照顺序存储,即有序性。在客户端的请求到达RegionServer后,HBase为了保证RowKey的有序性,不会将数据当即写入到HFile中,而是将每一个执行动做的数据保存在内存中,即MemStore中。MemStore可以很方便的兼容操做的随机写入,而且保证全部存储在内存中的数据是有序的。当MemStore到达阀值时,HBase会触发Flush机制,将MemStore中的数据Flush到HFile中,这样便能充分利用HDFS写入大文件的性能优点,提供数据的写入性能。学习

  整个读写流程,以下所示:this

 

  因为MemStore是存储放在内存中的,若是RegionServer因为出现故障或者进程宕掉,会致使内存中的数据丢失。HBase为了保证数据的完整性,这存储设计中添加了一个WAL机制。每当HBase有更新操做写数据到MemStore以前,会写入到WAL中(Write AHead Log的简称)。WAL文件会经过追加和顺序写入,WAL的每一个RegionServer只有一个,同一个RegionServer上的全部Region写入到同一个WAL文件中。这样即便某一个RegionServer宕掉,也能够经过WAL文件,将全部数据按照顺序从新加载到内容中。spa

 2.2 读取流程

   HBase查询经过RowKey来获取数据,客户端应用程序根据对应的RowKey来获取其对应的Region地址。查找Region的地址信息是经过HBase的元数据表来获取的,即hbase:meta表所在的Region。经过读取hbase:meta表能够找到每一个Region的StartKey、EndKey以及所属的RegionServer。因为HBase的RowKey是有序分布在Region上,因此经过每一个Region的StartKey和EndKey来肯定当前操做的RowKey的Region地址。设计

  因为扫描hbase:meta表会比较耗时,因此客户端会存储表的Region地址信息。当请求的Region租约过时时,会从新加载表的Region地址信息。日志

2.3 Flush机制

  RegionServer将数据写入到HFile中不是同步发生的,是须要在MemStore的内存到达阀值时才会触发。RegionServer中全部的Region的MemStore的内存占用量达到总内存的设置占用量以后,才会将MemStore中的全部数据写入到HFile中。同时会记录以及写入的数据的顺序ID,便于WAL的日志清理机制定时删除WAL的无用日志。code

  MemStore大小到达阀值后会Flush到磁盘中,关键参数由hbase.hregion.memstore.flush.size属性配置,默认是128MB。在Flush的时候,不会当即去Flush到磁盘,会有一个检测的过程。经过MemStoreFlusher类来实现,具体实现代码以下所示:

private boolean flushRegion(final FlushRegionEntry fqe) {
    HRegion region = fqe.region;
    if (!region.getRegionInfo().isMetaRegion() &&
        isTooManyStoreFiles(region)) {
      if (fqe.isMaximumWait(this.blockingWaitTime)) {
        LOG.info("Waited " + (EnvironmentEdgeManager.currentTime() - fqe.createTime) +
          "ms on a compaction to clean up 'too many store files'; waited " +
          "long enough... proceeding with flush of " +
          region.getRegionNameAsString());
      } else {
        // If this is first time we've been put off, then emit a log message.
        if (fqe.getRequeueCount() <= 0) {
          // Note: We don't impose blockingStoreFiles constraint on meta regions
          LOG.warn("Region " + region.getRegionNameAsString() + " has too many " +
            "store files; delaying flush up to " + this.blockingWaitTime + "ms");
          if (!this.server.compactSplitThread.requestSplit(region)) {
            try {
              this.server.compactSplitThread.requestSystemCompaction(
                  region, Thread.currentThread().getName());
            } catch (IOException e) {
              LOG.error(
                "Cache flush failed for region " + Bytes.toStringBinary(region.getRegionName()),
                RemoteExceptionHandler.checkIOException(e));
            }
          }
        }

        // Put back on the queue.  Have it come back out of the queue
        // after a delay of this.blockingWaitTime / 100 ms.
        this.flushQueue.add(fqe.requeue(this.blockingWaitTime / 100));
        // Tell a lie, it's not flushed but it's ok
        return true;
      }
    }
    return flushRegion(region, false, fqe.isForceFlushAllStores());
  }

  从实现方法来看,若是是MetaRegion,会马上进行Flush,缘由在于Meta Region优先级高。另外,判断是否是有太多的StoreFile,这个StoreFile是每次MemStore Flush产生的,每Flush一次就会产生一个StoreFile,因此Store中会有多个StoreFile,即HFile。

  另外,在HRegion中也会检查Flush,即经过checkResources()方法实现。具体实现代码以下所示:

private void checkResources() throws RegionTooBusyException {
    // If catalog region, do not impose resource constraints or block updates.
    if (this.getRegionInfo().isMetaRegion()) return;

    if (this.memstoreSize.get() > this.blockingMemStoreSize) {
      blockedRequestsCount.increment();
      requestFlush();
      throw new RegionTooBusyException("Above memstore limit, " +
          "regionName=" + (this.getRegionInfo() == null ? "unknown" :
          this.getRegionInfo().getRegionNameAsString()) +
          ", server=" + (this.getRegionServerServices() == null ? "unknown" :
          this.getRegionServerServices().getServerName()) +
          ", memstoreSize=" + memstoreSize.get() +
          ", blockingMemStoreSize=" + blockingMemStoreSize);
    }
  }

  代码中的memstoreSize表示一个Region中全部MemStore的总大小,而其总大小的结算公式为:

  BlockingMemStoreSize = hbase.hregion.memstore.flush.size * hbase.hregion.memstore.block.multiplier

  其中,hbase.hregion.memstore.flush.size默认是128MB,hbase.hregion.memstore.block.multiplier默认是4,也就是说,当整个Region中全部的MemStore的总大小超过128MB * 4 = 512MB时,就会开始出发Flush机制。这样便避免了内存中数据过多。

3. Compaction

  随着HFile文件数量的不断增长,一次HBase查询就可能会须要愈来愈多的IO操做,其 时延必然会愈来愈大。于是,HBase设计了Compaction机制,经过执行Compaction来使文件数量基本保持稳定,进而保持读取的IO次数稳定,那么延迟时间就不会随着数据量的增长而增长,而会保持在一个稳定的范围中。

  而后,Compaction操做期间会影响HBase集群的性能,好比占用网络IO,磁盘IO等。所以,Compaction的操做就是短期内,经过消耗网络IO和磁盘IO等机器资源来换取后续的HBase读写性能。

  所以,咱们能够在HBase集群空闲时段作Compaction操做。HBase集群资源空闲时段也是咱们清楚,可是Compaction的触发时段也不能保证了。所以,咱们不能在HBase集群配置自动模式的Compaction,须要改成手动定时空闲时段执行Compaction。

  Compaction触发的机制有如下几种:

  1. 自动触发,配置hbase.hregion.majorcompaction参数,单位为毫秒
  2. 手动定时触发:将hbase.hregion.majorcompaction参数设置为0,而后定时脚本执行:echo "major_compact tbl_name" | hbase shell
  3. 当选中的文件数量大于等于Store中的文件数量时,就会触发Compaction操做。由属性hbase.hstore.compaction.ratio决定。

  至于Region分裂,经过hbase.hregion.max.filesize属性来设置,默认是10GB,通常在HBase生产环境中设置为30GB。

4.总结

  在作Compaction操做时,若是数据业务量较大,能够将定时Compaction的频率设置较短,好比:天天凌晨空闲时段对HBase的全部表作一次Compaction,防止在白天繁忙时段,因为数据量写入过大,触发Compaction操做,占用HBase集群网络IO、磁盘IO等机器资源。

5.结束语

  这篇博客就和你们分享到这里,若是你们在研究学习的过程中有什么问题,能够加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。 

相关文章
相关标签/搜索