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 条评论
登录 后参与评论

相关文章

来自专栏牛客网

快手Java开发面经(2技术面)

如果你要做一件事,请不要炫耀,也不要宣扬,只管安安静静的去做。因为那是你自己的事,别人不知道你的情况,也不可能帮你去实现。千万不要因为虚荣心而炫耀。也不要因为别...

712
来自专栏Golang语言社区

gRPC服务发现&负载均衡

构建高可用、高性能的通信服务,通常采用服务注册与发现、负载均衡和容错处理等机制实现。根据负载均衡实现所在的位置不同,通常可分为以下三种解决方案:

1822
来自专栏瓜大三哥

创建基本时钟周期约束

Xilinx建议把时序约束和物理约束分开来写。(但是必须有一个作为target) 时序约束用于综合和实现,物理约束用于实现。 1时钟周期 2占空比 3相位 ?...

1766
来自专栏IT笔记

SpringBoot开发案例之整合mail发送服务

? spring-boot-mail.jpg 记得上个月做过这样一篇笔记,微服务架构实践之邮件通知系统改造。 当时用的是开源的第三方插件mail和Thymel...

3197
来自专栏java 成神之路

Hystrix 服务降级-后备模式 实现思路

Hystrix 提供了服务降级功能。 有些场景下当调用服务失败时,不应该产生一个Exception 异常给用户。而是采用执行备用策略。

701
来自专栏Golang语言社区

Golang中的sync.WaitGroup用法实例

WaitGroup的用途:它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成。 官方对它的说明如下: A...

41314
来自专栏Golang语言社区

54. 心跳的实现 | 厚土Go学习笔记

在多客户端同时访问服务器的工作模式下,首先要保证服务端的运行正常。因此,Server在和Client建立通讯后,确保连接的及时断开就非常重要。否则,多个客户端长...

27510
来自专栏Java技术分享

第十章:Shiro的Cache——深入浅出学Shiro细粒度权限开发框架

概述 Shiro开发团队明白在许多应用程序中性能是至关重要的。Caching 是Shiro 中的一个重要功能,以确保安全操作保持尽可能的快。 但是,Shiro并...

4389
来自专栏流浪猫的golang

go 的入门之路 Socket 编程

服务端监听本地IP(127.0.0.1)7777端口。当有客户端连接时,获得一个conn 对象,coon 对象是 interface Conn的实现者,

733
来自专栏python学习指南

Python爬虫(二十)_动态爬取影评信息

本案例介绍从JavaScript中采集加载的数据。更多内容请参考:Python学习指南 #-*- coding:utf-8 -*- import req...

2789

扫码关注云+社区