hbase-memstore flush剖析

    memstore是hbase中一个非常重要的组件,对于hbase的读写操作的性能起到举足轻重的作用,下面讲从memstore概述、memstore flush触发条件、memstore flush流程、memstore flush流程源码四个方面对memstore进行说明

memstore概述 

    一个RegionServer上对应多个Region,一个Region对应多个Store,一个Store对应一个Memstore和多个HFile,几者之间的关系如下图

Regionserve框架图

    hbase写操作首先写入wal,然后写入Memstore,当达到Memstore flush的条件之后将Memstore的数据批量写入磁盘,生成一个新的Hfile文件。这种先写Memstore内存后批量写入磁盘的方式大大提升了hbase的写入性能。hbase读操作首先检查数据是否在Memstore中,未命中然后再到blockcache中查找,如果还没有命中则到Hfile中去查找,最后merge一个结果返回。由于新写入的数据会在Memstore中,而新写入的数据被读取的概率在大多场景中是比较频繁的。可见Memstore在hbase的读写中扮演着多么重要的角色。

memstore flush触发条件

    Memstore flush是以Region作为单位,而不是单个Memstore,当满足条件需要进行Memstore flush时会获取该Region上满足条件的store进行Memstore flush,这就是为什么官网建议一个表不要定义太多列簇(一个列簇对应一个store),当多个列簇中的一个列簇对应的store中的Memstore达到了flush条件,会导致该Region上其他store中的Memstore也会进行flush,从而导致flush之后生成Hfile小文件。

    以下6个条件满足其一就会触发Memstore flush

1)Region上的任意一个memstore大小大于hbase.hregion.memstore.flush.size的值(默认128M)。具体源码可以查看:HRegion.isFlushSize()方法。

2)如果region上总的memstore的大小大于blockingMemStoreSize = hbase.hregion.memstore.block.multiplier*hbase.hregion.memstore.flush.size,写入操作会报错RegionTooBusyException。具体源码可以查看:HRegion.checkResources()方法

3)周期性(周期值:hbase.server.thread.wakefrequency)线程检查Regionserver上所有的store,当store中memestore的最后一个操作时间与当前时间相差hbase.regionserver.optionalcacheflushinterval(默认1小时)则需要进行flush 。具体源码可以查看:PeriodicMemstoreFlusher.chore()

4)RegionServer上的所有memstore的总内存大于globalMemStoreLimitLowMark,则会进行flush,并阻塞,直到所有memstore的总内存小于globalMemStoreLimitLowMark。具体源码可以查看:MemStoreFlusher.reclaimMemStoreMemory()方法。

globalMemStoreLimit 和globalMemStoreLimitLowMark 需要明确:

globalMemStoreLimit = heap*hbase.regionserver.global.memstore.size(hbase.regionserver.global.memstore.upperLimit老版本)

globalMemStoreLimitLowMark = globalMemStoreLimit*globalMemStoreLimitLowMarkPercent(hbase.regionserver.global.memstore.size.lower.limit或hbase.regionserver.global.memstore.lowerLimit 老版本)

5)当RegionServer中Hlog的数量达到上限hbase.regionserver.maxlogs,则会选取最早一个Hlog对应的一个或多个Region进行flush。具体源码可以查看:FSHLog.findRegionsToForceFlush()方法.

6)手动执行flush,通过shell命令 flush ‘tablename’或者flush ‘region name’分别对一个表或者一个Region进行flush。

memstore flush流程

    Memstore flush使用的是生产-消费模式,当达到memstore flush的条件会生产一条flush request到DelayQueue队列(DelayQueue详情请看“多线程并发编程15-DelayQueue源码剖析”),消费线程(默认2个,可以通过hbase.hstore.flusher.count参数进行修改)持续从DelayQueue中获取flush request请求进行flush。flush的流程如图所示:

Memstore flush 流程图

1)达到“Memstoreflush触发条件”这节所描述的6个条件之一,则发送一个flush request到并发队列DelayQueue中。

2)消费线程持续从并发队列DelayQueue中获取flush request进行flush。

3)获取Region证据拍每个需要flush的store,如果为强制flush则获取所有的store,否则根据选择策略选取满足条件的store。

两种store选择策略:

1.FlushLargeStoresPolicy(默认),选取store中Memstore大于指定阈值(默认16M)的store,如果没有大于指定阈值的store则返回所有的store

2.FlushAllStoresPolicy,选取所有的store。

4)prepare阶段,创建一个Memstore的快照snapshot,将当前状态下的Memstore的CellSkipListSet赋值为snapshot,并创建一个新的CellSkipListSet,之后写入Memstore的数据就存放在新的CellSkipListSet中。在整个flush阶段读操作会分别遍历snapshot和新的CellSkipListSet。prepare阶段需要获取锁,对写请求阻塞,由于该阶段并没有耗时操作,阻塞的时间很短。

5)flush阶段,遍历所有Memstore,将prepare阶段生成的snapshot持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及到磁盘IO操作,因此相对比较耗时。

6)commit阶段,遍历所有的Memstore,将flush阶段生成的临时文件移到指定的ColumnFamily目录下,针对HFile生成对应的storefile和Reader,把storefile添加到HStore的storefiles列表中,最后再清空prepare阶段生成的snapshot。

memstore flush流程源码

    下面只贴出memstore flush主要流程的源码,具体的请到源码中进行查看。

flushcache:

    Memstore flush入口函数。

public FlushResult flushcache(boolean forceFlushAllStores, boolean writeFlushRequestWalMarker)

    throws IOException {

......

    try {

      // (1)获取需要flush的store,如果为强制flush则获取所有的store,否则根据选择策略选取满足条件的store

      // 两种store选择策略

      //1.FlushLargeStoresPolicy(默认),选取store中memstore大于指定阈值(默认16M)的store,如果没有大于指定阈值的store则返回所有的store

      //2.FlushAllStoresPolicy,选取所有的store

      Collection<Store> specificStoresToFlush =

          forceFlushAllStores ? stores.values() : flushPolicy.selectStoresToFlush();

//(2)将选取出的store进行flush。

      FlushResult fs = internalFlushcache(specificStoresToFlush,

        status, writeFlushRequestWalMarker);

......

}

internalFlushcache:

protected FlushResult internalFlushcache(final WAL wal, final long myseqid,

    final Collection<Store> storesToFlush, MonitoredTask status, boolean writeFlushWalMarker)

        throws IOException {

//(1)进行Prepare阶段

  PrepareFlushResult result

    = internalPrepareFlushCache(wal, myseqid, storesToFlush, status, writeFlushWalMarker);

//(2)进行flush阶段和commit阶段

  if (result.result == null) {

    return internalFlushCacheAndCommit(wal, status, result, storesToFlush);

  } else {

    return result.result; // early exit due to failure from prepare stage

  }

}

snapshot:

    准备阶段最主要的就是进行快照的生成。

public MemStoreSnapshot snapshot() {

......

    this.snapshotId = EnvironmentEdgeManager.currentTime();

    this.snapshotSize = keySize();

    if (!this.cellSet.isEmpty()) {

//(1)将 当前状态的Memstore的cellset赋值给snapshot

      this.snapshot = this.cellSet;

//(2)创建一个新的CellSkipListSet赋值给Memstore的cellSet 

      this.cellSet = new CellSkipListSet(this.comparator);

.......

}

internalFlushCacheAndCommit

    flush阶段和commit阶段。

protected FlushResult internalFlushCacheAndCommit(

      final WAL wal, MonitoredTask status, final PrepareFlushResult prepareResult,

      final Collection<Store> storesToFlush)

  throws IOException {

......

    // (1)在tmp目录下创建文件,将快照中数据写入该文件中

    for (StoreFlushContext flush : storeFlushCtxs.values()) {

      flush.flushCache(status);

    }

    // Switch snapshot (in memstore) -> new hfile (thus causing

    // all the store scanners to reset/reseek).

    Iterator<Store> it = storesToFlush.iterator();

    // stores.values() and storeFlushCtxs have same order

    for (StoreFlushContext flush : storeFlushCtxs.values()) {

      //(2)将tmp目录下的临时文件移动到指定cf目录下,将Hfile添加到StoreFileManager中,并清除快照

      boolean needsCompaction = flush.commit(status);

      if (needsCompaction) {

        compactionRequested = true;

      }

      byte[] storeName = it.next().getFamily().getName();

      List<Path> storeCommittedFiles = flush.getCommittedFiles();

      committedFiles.put(storeName, storeCommittedFiles);

      // Flush committed no files, indicating flush is empty or flush was canceled

      if (storeCommittedFiles == null || storeCommittedFiles.isEmpty()) {

        totalFlushableSizeOfFlushableStores -= prepareResult.storeFlushableSize.get(storeName);

      }

      flushedOutputFileSize += flush.getOutputFileSize();

    }

    storeFlushCtxs.clear();

......

}

     今天的分享就到这,有看不明白的地方一定是我写的不够清楚,所有欢迎提任何问题以及改善方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349