Hbase Memstore 读写及 flush 源码分析

导语

本文档主要从源码的角度分析了,hbase的写缓存的读写以及flush过程。因为在分析wal的过程中已经把写分析的比较详尽了,而因为memstore是内存结构读的过程比较简单,本文档概要说明memstore的读写,着重分析flush过程。

写的角度

1.准备工作,主要是和Coprocessor相关

2.获得行锁,把所有要更新的行锁都拿到通过getRowLockInternal(byte[] row, boolean waitForLock),这个方法会在每个锁上都轮询一直拿到所有row的锁。

有关原理可以参考:

http://blog.csdn.net/lipeng_bigdata/article/details/50458771

3.获得region的update锁,具体的说是java.util.concurrent.locks包中ReentrantReadWriteLock的读锁。

HRegion中声明了两个锁,分别是lock和updatelock,这里解释下。这两个锁均为ReentrantReadWriteLock类型的读写锁,其中,lock用于Region的close、compact、flush等的并发控制,它控制的是Region的整体行为,更具体的,compact()和flushCache()方法中,用的是lock的读锁--共享锁,而doClose()方法中,用的是lock的写锁--独占锁,这也就意味着,在Region下线,执行doClose()方法时,它必须等待compact()和flushCache()方法调用完,且一旦它获得了lock的写锁,后续Region将不会再执行Region的compact和flush,当然,doClose()内部仍然会在下线前flush掉它的memstore,同时共享锁业也实现了Region的flush和compact在理论上可以同时进行。而updatesLock则用于Region数据更新方面,在flush的核心方法internalFlushcache()中,则是使用的updatesLock的写锁。

doProcessRowWithTimeout-》让 processor(MultiRowMutationProcessor 用于执行多个 put/delete)扫描rows,生成mutations和waledits。(Let the processor scan the rows, generate mutations and add waledits)

主要是检查cf并生成时间戳并把同一行的更新Cell放到一个WALEdit中。

这里又生成一个mutations的原因是区分put和delete

6.mvcc 开始处理,也即是把通过生成的mvccId生成写Id并把当前cell放入mvcc的写队列。hbase的mvcc机制将结合行锁在后面的hbase效率的源码分析中具体分析。可先参考:

http://m.blog.csdn.net/article/details?id=43836701

7.预处理

8.和memstore应用的相关,遍历mutations,通过getStore获得HStore实例,把这些cell添加到store中。memstore中根据不同的CF对应了不同的HStore实例,HStore实例又对应了多个HFile。memstore的实际内存映射就是这些HStore。

8.append到Hlog中,准确的应该是append到RingBuffer中。详见WAL的文章

9.释放region的update锁,即3中获得的锁。

10.释放所有的row锁,即2中获得的锁。

11.同步editlog,准确的说是通知RingBuffer。

调用完成了Put的回调

-------------------->写的过程中通过调用requestFlush()方法来进行memstore的刷写。

调用所在RegionServer的MemStoreFlusher。requestFlush方法进行刷写。

读的角度

hbase的读需要从要读三个位置,blockcache、memstore和hfile着手。

大概过程是先从blockcache中读,如果没有则去memstore和hfile中去读,先用布隆过滤器把一定不可能的hfile去除,再使用scanner按时间降序扫描到需要的keyvalues,最后把相应块加到blockcache中去并发还给client端。

因为包括了整个读过程。

原理参考:

http://hbasefly.com/2016/12/21/hbase-getorscan/

http://blackproof.iteye.com/blog/2007981

和《权威指南》P327

http://hbasefly.com/2016/04/26/hbase-blockcache-2/

接下来分析本文档的重点:

Flush的角度

从memstore刷写时机(上一篇文档着重叙述)来看,有六种情况:单个memstore,一个region中,整个regionserver,Hlog,定期,和人工。

尽管触发memstore的条件很多,但实际执行memstore的flush是调用对应的HRegion的flushcache方法开始的。它的原型如下:

调用这个方法,只有在cache(memstore)为空,region已经关闭,当前flush正在进行和不能写情况下。

其调用基本过程如下:

其中flushcache()和internalFlushcache都是HRegion中的方法。

1.最开始flushcache会使用HRegion用于应对region并发控制的lock(前面有介绍)加锁。2.然后使用synchronize获得WriteState结构的状态,这个类主要用于保证Region级别的flush、compact时的状态一致。即是说有多个线程调用flushcache时,先获得这个对象的去flush。3.获取需要去flush的HStore,如果参数forceFlushAllStores为true的话,就会flush当前region上的所有stores,如果为false的话,根据配置的FlushPolicy(hbase.regionserver.flush.policy,默认是FlushLargeStoresPolicy)选择部分stores来flush。

然后调用internalFlushCache方法,这个方法实际去执行,选择一些stores到hfile(图中蓝色部分)。当刷写成功后将标记成功的memstore,并通知因writeState而阻塞的线程。最后释放lock。

在internalFlushCache方法中把flush memstore分成两部分,第一部分是准备(interPrepareFlushCache):主要是去准备一些中间数据结构和以及当前memstore的快照,这个快照的作用是在flush memstore的同时并不妨碍client对memstore的读写;第二部分是把快照刷写到一个临时目录中,然后再把临时目录中数据移到正式目录,要把具体的刷写分成这两步的就像是两阶段提交,刷写到临时目录就是确认过程而后一步就是提交过程。

我们知道hbase通过精心的设计成一个以顺序写见长的数据存储系统,而memstore刷写时的快照即是其中精心设计的部分。原理其实很简单,为了不中断读写,在prepare部分,新建一个新的memstore(HStore)并把相关指标清零,旧的memstore就作为快照刷入HFile。因为memstore都是内存操作,所以这个转换是很快的。当然在转换的过程中,update的操作会被暂停一段时间。

prepare部分另一个中间数据结构分别为:totalFlushableSizeOfFlushableStores,storeFlushCtxs,committedFiles,storeFlushableSize,比较重要的是storeFlushCtxs和committedFiles。他们都被定义为以CF做key的TreeMap,分别代表了store的CF实际执行(StoreFlusherImpl)和最终刷写的HFlile文件:

StoreFlusherImpl是HStore的内部类,它实现了StoreFlushContext的prepare,flushCache以及commit方法,这几个方法用于完成准备和刷写HStore的操作。其类图如下:

在第二部分的internalFlushCacheAndCommit刷写Hfile到临时目录和转到正式目录就比较清晰了,使用两阶段提交直接调用StoreFusherImpl的flushCache和commit方法。

稍微需要注意的是,在flush的时候有可能会失败,这时候意味着memstore未被持久化,则wal需要去重做,会启动一个单独且唯一的线程去做这个,从源码注释上看,当前只有regionserver重启会发生这种事情。

至此,memstore的读写已经刷入源码分析就结束了,可以看到memstore作为hbase写缓存为了实现快速顺序写做出的设计努力。下一篇文档将分析哪些情况下memstore会被刷写。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大数据文摘

手把手 | 20行Python代码教你批量将PDF转为Word

36150
来自专栏代码世界

Python中的logger和handler到底是个什么鬼

最近的任务经常涉及到日志的记录,特意去又学了一遍logging的记录方法。跟java一样,python的日志记录也是比较繁琐的一件事,在写一条记录之前,要写好多...

34590
来自专栏Web项目聚集地

Javascript中的异步

9020
来自专栏bboysoul

linux 终端下最简单的代理方式(proxychains)

我以前写过给linux终端设置代理 这个是用polipo这个工具把socks5代理转换成为http和https代理来实现终端下代理的,那么终端下有没有原生的使...

24920
来自专栏Linyb极客之路

杂谈Java高并发

对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了。而并发问题是绝大部分的程序员头疼的问题,但话又说回来了,既然逃避不掉,那...

63050
来自专栏happyJared

程序员神器,IntelliJ IDEA 2018.1 正式发布

3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再次让人眼前一亮:什么,还能这么玩?

11310
来自专栏Youngxj

博客彩虹友链源码

15450
来自专栏Jaycekon

Python-WXPY实现微信监控报警

概述:   本文主要分享一下博主在学习wxpy 的过程中开发的一个小程序。博主在最近有一个监控报警的需求需要完成,然后刚好在学习wxpy 这个东西,因此很巧妙的...

2.9K80
来自专栏静晴轩

Gulp探究折腾之路(I)

前言: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器;她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完...

38080
来自专栏cnblogs

Hi,给他介绍一款markdown的帮助文档生成器

      当今大多数的团队都实现了前、后端分支。前端与后端的沟通都是通过接口来实现的(一般情况下都是webapi接口)。这种情况你肯定需要一个接口查询的帮助文...

14620

扫码关注云+社区

领取腾讯云代金券