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

相关文章

来自专栏更流畅、简洁的软件开发方式

用node.js实现ORM的一种思路

  ORM是O和R的映射。O代表面向对象,R代表关系型数据库。二者有相似之处同时也各有特色。就是因为这种即是又非的情况,才需要做映射的。   理想情况是,根据关...

2079
来自专栏搜云库

Spring Boot 中使用 MongoDB 增删改查

本文快速入门,MongoDB 结合SpringBoot starter-data-mongodb 进行增删改查

4037
来自专栏云加头条

腾讯云 CMQ 消息队列测试

最近收到腾讯云提供 CMQ 的内测体验资格,于是对其中的消息队列服务进行消息分发和管理作出测试,并将测试结果以及所遇到的问题进行了汇总。

2.7K0
来自专栏Linux驱动

9.按键之使用异步通知(详解)

之前学的应用层都是: 1)查询方式:一直读 2)中断方式.同样一直读,直到中断进程唤醒 3)poll机制:一直在poll函数中睡眠,一定时间读一次 以上3种,我...

1889
来自专栏linux驱动个人学习

Linux下进程的创建过程分析(_do_fork do_fork详解)--Linux进程的管理与调度(八)

Unix标准的复制进程的系统调用时fork(即分叉),但是Linux,BSD等操作系统并不止实现这一个,确切的说linux实现了三个,fork,vfork,cl...

692
来自专栏依乐祝

Redis基本使用及百亿数据量中的使用技巧分享

熟悉的开场白,大家晚上好啊,今天给大家分享的是Redis在大数据中的使用,可能真正讲的是一些redis的使用技巧,Redis基本的一些东西。

820
来自专栏大学生计算机视觉学习DeepLearning

c++ 网络编程(三)TCP/IP LINUX/windows 进程间的通信原理与实现代码 基于多进程的服务端实现

原文链接:https://www.cnblogs.com/DOMLX/p/9613027.html

1484
来自专栏程序员的SOD蜜

移花接木:当泛型方法遇上抽象类----我的“内存数据库”诞生记

之前,不怕“重复发明轮子”的我,搞了一个“PDF.NET框架”,即“PWMIS数据开发框架”(目前已经开源),自己用特殊的方式设计了一个实体类基类,然后又设计了...

4185
来自专栏web前端教室

先行者计划--1107微课 《什么是Vuex?》| 文字简版

vuex是什么东西? 官网对Vuex的定义,"Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。" 恩,从这句话可以看出,它应该是一种编写js的...

2039
来自专栏Coding01

简述我所理解的 PHP Trait

在常规的 PHP 开发中,我们都习惯于先编写一个通用的基类,实现基本的功能,然后扩展这个基类,创建更具体的子类,直接从父类继承实现。很多编程语言都使用这个继承层...

895

扫码关注云+社区