HBase源码分析之memstore的实现及flush处理流程

本文深入剖析了HBase Memstore的实现,包括数据存储在cellSet中,使用MSLAB解决内存碎片问题。详细阐述了flush的触发方式,如手动触发、自动触发(内存大小、全局内存比例等条件)。分析了flush处理流程的prepare、flush和commit三个阶段,以及split和compact的触发条件。最后,讨论了关键配置参数对性能的影响,建议合理配置以平衡读写操作并减少region中的ColumnFamily数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

memstore作为hbase数据写入的核心部分,保障了数据的高效的写入,那其具体是怎么实现的?使用了哪些优化的手段?有哪些关键要素可以控制写入的效率呢?下面我们先来分析一下memstore的实现

memstore中的数据存储

DefaultMemStore.java
// 2个存储数据的集合
volatile CellSkipListSet cellSet;
volatile CellSkipListSet snapshot;

cellSet:写入的数据都会存储在cellSet中,snapshot是在memstore做flush时才用到的,这个我们后续再说
我们先来看往memstore中添加数据的情况

public Pair<Long, Cell> add(Cell cell) {
  Cell toAdd = maybeCloneWithAllocator(cell);
  return new Pair<Long, Cell>(internalAdd(toAdd), toAdd);
}

这里使用到了一个叫MSLAB的机制,这个是用来解决HBase因 Region flush导致的内存碎片问题,按固定大小(默认2M)的方式来申请内存。通过internalAdd方法将cell添加到cellSet中。
如果要回滚数据呢,也很简单从cellSet中删除即可,当然有可能此时数据会在snapshot中,那就从snapshot中删除。接着我们看看flush的流程

flush触发方式

HBase会在如下几种状态下触发flush操作,
1、手工触发方式
通过hbase shell命令或者通过java api HBaseAdmin,可以指定flush单个region或者整个表。flush单个region时请求是直接提交到regionserver的,而flush整个表时请求是提交到master节点,由master节点向各regionserver发送请求

2、自动触发方式
自动触发的方式有如下几种:
2.1、在region的写操作前(如put\delete\append\batchMutate等)的checkResources方法中,如果region的memstoresize>{hbase.hregion.memstore.flush.size*hbase.hregion.memstore.block.multiplier 默认128M*4=512M}
2.2、在region的写操作过程中检查region的memstoresize>{hbase.hregion.memstore.flush.size 默认128M}
2.3、在region的写操作过程中或者flush运行的线程中,调用MemStoreFlusher.reclaimMemStoreMemory检查regionserver的整个memstoresize>高水位线{hbase_maxheapsize*hbase.regionserver.global.memstore.size 默认值0.4。不能超过0.8超过会按默认值处理}
memstoresize>低水位线{hbase_maxheapsize* hbase.regionserver.global.memstore.size * hbase.regionserver.global.memstore.size.lower.limit 默认值0.95}
由此触发的flush会一直循环选取region进行flush处理,直至memstoresize低于低水位线。

 选取region的规则为:选取memstoreSize最大的region,这里会选2个region出来
   a. bestFlushableRegion
     memstoreSize最大且不会有太多的storefile(判断条件storefileNum<=hbase.hstore.blockingStoreFiles配置值,默认7) 
   b. bestAnyRegion:memstoreSize最大的。
 如果bestFlushableRegion为null或者bestAnyRegion.getMemstoreSize()>2 *bestFlushableRegion.getMemstoreSize()时选取bestAnyRegion,否则选择bestFlushableRegion. 如果高于高水位线,会导致写入操作block住,但是这里只会等待5秒,blockSignal.wait(5 * 1000); 
 触发高水位线时,在regionserver的日志中对打印类似信息: Memstore is above high water mark and block...

2.4、 由regionserver中的PeriodicMemstoreFlusher线程来触发,每10秒(由hbase.server.thread.wakefrequency参数配置默认10000,单位毫秒)检查一次。该线程会检查所在regionserver的全部在线region,满足以下条件将会触发flush:

a. 最后一次flush后的变更数超过{hbase.regionserver.flush.per.changes 默认值 30000000 3千万}配置值
b. memstore中的最老的记录的时间戳与当前时间的间隔超过配置值{hbase.regionserver.optionalcacheflushinterval 默认3600000 1个小时},如果是meta表的region则时间为5分钟。
为避免同时提交flush的请求太多,这里有一个3~23秒的随机延迟

2.5、regionserver有一个日志滚动线程:final LogRoller walRoller,如果regionserver的HLog数量超过了限制(参数hbase.regionserver.maxlogs 默认值32),会选取最早的一个HLog中的region(一个或多个)进行flush

flush处理流程

flush的处理是个生产者消费者模式,通过MemStoreFlusher.requestFlush或者requestDelayedFlush来收集请求,通过多个FlushHandler(个数由参数hbase.hstore.flusher.count配置默认值2)来进行flush处理。使用的消息队列是用的DelayQueue
flush前会对region的状态做一些判断,如果为关闭或者正在flushing的不能做flush处理。
整个flush的过程可以分为如下3个阶段:

  1. prepare阶段
    方法:internalPrepareFlushCache,这里会将region下所有的memstore都生成对应的snapshot,为防止其他的线程同时更新memstore,这里会加上一个排它锁。prepare的操作只是将cellSet赋值给snapshot,然后在new一个CellSkipListSet,所以这个锁的时间会很短。
  2. flush阶段
    将prepare后生成的所有snapshot持久化到hdfs上为一个hfile,存放在region目录下的.tmp目录中。
  3. commit阶段
    将flush阶段生成的临时文件移动到对应的columnfamily目录下,然后将这些storefile建立StoreFile对象,添加到StoreFileManager对象中,清空snapshot,注意这里会对MemStoreLAB snapshotAllocator 做close处理,如果开启了ChuckPool,会把这些chuck添加到chuckPool中。memstoreSize减去flush的大小,更新maxFlushedSeqId。

在flush完成成后如果需要split处理会做split处理,如果需要compact会做compact处理。
split和compact的触发条件和处理流程后续再说。

总结

flush相关较重要配置参数(其他参数使用默认值),这里只列举最新的参数。
jvm heapsize
hbase.regionserver.global.memstore.size
hbase.regionserver.global.memstore.size.lower.limit
hbase.hregion.memstore.flush.size
hbase.hregion.memstore.block.multiplier
hbase.hstore.flusher.count

通过对hbase的flush触发条件和处理流程的分析,为保障flush的处理避免对hbase读写操作造成影响,可以遵循以下限制来合理配置各项参数信息
1、memstoresize不能设置太小,以免频繁触发高水位线导致写入被block;由于读操作主要靠cache的命中来提高响应时间,所以memstoresize也不能太过于浪费。
2、FlushHandler的个数要保证能及时将memstore的数据写入hdfs
3、要结合memstoresize的大小来考虑每个regionserver上分配的region的个数。

另需注意提交flush时,是按region来提交的,所以如果一个region中的memstore过多的话,每次flush的开销就会较大,所以我们设计表时尽量减少ColumnFamily的个数。

参考资料:
http://hbasefly.com/2016/03/23/hbase-memstore-flush/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值