客户端读写数据是先从Zookeeper中获取RegionServer的元数据信息,好比Region地址信息。在执行数据写操做时,HBase会先写MemStore,为何会写到MemStore。本篇博客将为读者剖析HBase MemStore和Compaction的详细内容。shell
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上。性能
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
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地址信息。日志
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机制。这样便避免了内存中数据过多。
随着HFile文件数量的不断增长,一次HBase查询就可能会须要愈来愈多的IO操做,其 时延必然会愈来愈大。于是,HBase设计了Compaction机制,经过执行Compaction来使文件数量基本保持稳定,进而保持读取的IO次数稳定,那么延迟时间就不会随着数据量的增长而增长,而会保持在一个稳定的范围中。
而后,Compaction操做期间会影响HBase集群的性能,好比占用网络IO,磁盘IO等。所以,Compaction的操做就是短期内,经过消耗网络IO和磁盘IO等机器资源来换取后续的HBase读写性能。
所以,咱们能够在HBase集群空闲时段作Compaction操做。HBase集群资源空闲时段也是咱们清楚,可是Compaction的触发时段也不能保证了。所以,咱们不能在HBase集群配置自动模式的Compaction,须要改成手动定时空闲时段执行Compaction。
Compaction触发的机制有如下几种:
至于Region分裂,经过hbase.hregion.max.filesize属性来设置,默认是10GB,通常在HBase生产环境中设置为30GB。
在作Compaction操做时,若是数据业务量较大,能够将定时Compaction的频率设置较短,好比:天天凌晨空闲时段对HBase的全部表作一次Compaction,防止在白天繁忙时段,因为数据量写入过大,触发Compaction操做,占用HBase集群网络IO、磁盘IO等机器资源。
这篇博客就和你们分享到这里,若是你们在研究学习的过程中有什么问题,能够加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉。