引言:
在HBase的架构设计中,为了降低写入数据的延迟,将每个写请求分为了两个阶段,第一阶段是接收写请求并将数据写入内存,第二阶段是在后台批量地将数据刷写到磁盘。由此将内存的告诉随机写与磁盘的高速顺序写结合起来,已达到较低写入时延的目的。
基本原理:
在hbase系统中,regionserver会不断接收到写请求,并将数据写入memstore,每个region的每个Store都有自己对应的MemStore。当发现某个memstore写满或者系统状态满足一些条件时,则发起flush请求。整个flush请求的响应过程是异步的,会有后台常驻线程来处理,处理步骤主要是两步:第一步是创建memstore的snapshot,第二部是将snapshot写入对应文件路径形成新的hfile。
Flush涉及的类:
1.MemStoreFlusher:
a.flushQueue:用于保存已提交的flush request;
b.regionsInQueue:用于记录哪些region正在flush;
c.FlushHandler:一组flush线程,负责处理flush request。
2.HRegionServer:
在server初始化时,负责创建MemStoreFlusher。具体见initializeThreads方法。
// Cache flushing thread.
this.cacheFlusher = new MemStoreFlusher(conf, this);
3.HRegion:
该类负责管理对region的所有操作,因此也是flush请求最主要的发起者。在HRegion中保存了每个region对应的Flush策略(FlushPolicy)。
4.LogRoller:
该类的功能是不断滚动WAL日志,但是每次滚动时都需要保证日志对应的所有MemStore被flush了(需要刷写所有的MemStore),才可以进行日志滚动。
5.FlushPolicy:
Flush策略用于决定每次需要刷写哪些Store。
在1.0以前默认是将一个region下所有的MemStore都刷写到磁盘;在1.0之后默认是选择大小大于阈值的MemStore进行刷写。具体见FlushLargeStoresPolicy和FlushAllStoresPolicy。
Flush的触发点(以hbase1.2.4为例):
1.所有put类操作(包括Put,Delete,Increment,Append,BatchMutate)开始前都会checkResource,在这里检查memstore的大小,如果满足条件2,则发起请求。
2.在Put类操作后会再进行检查,如果满足条件1,则发起请求。具体涉及的方法:
1.batchMutate
2.processRowsWithLocks
3.append
4.doIncrement
3.LogRoller中在发起log roll时会强制将内存中的数据flush。
Flush需要满足的条件(以hbase1.2.4为例):
条件1.common flush
private boolean isFlushSize(final long size) {
return size > this.memstoreFlushSize; // size为当前region下所有MemStore大小的和。
}
memstoreFlushSize的默认大小是:134217728B=128MB,可以在HTableDescriptor中设置。
条件2.block flush
if (this.memstoreSize.get() > this.blockingMemStoreSize) {
requestFlush();
throw new RegionTooBusyException();
}
blockingMemStoreSize默认大小是: 2 * memstoreFlushSize,系数可以设置。
条件3. 文件选择
在1.0之前每次发起的flush请求会默认将改region下的所有memstore刷写,但是从1.0开始默认的策略改为了尽量只刷写大小大于阈值的memstore。
阈值由两个参数决定,取其中较大的一个。第一个参数可以通过hbase.hregion.percolumnfamilyflush.size.lower.bound设置,默认值为16777216;第二个参数为hbase.hregion.memstore.flush.size / column_family_number。
Flush的具体流程(代码分析):
To be continued