HBase源代码分析之HRegion上MemStore的flsuh流程(一)
了解HBase架构的用户应该知道,HBase是一种基于LSM模型的分布式数据库。LSM的全称是Log-Structured Merge-Trees。即日志-结构化合并-树。
相比于Oracle普通索引所採用的B+树,LSM模型的最大特点就是,在读写之间採取一种平衡,牺牲部分读数据的性能,来大幅度的提升写数据的性能。通俗的讲,HBase写数据如此快,正是因为基于LSM模型,将数据写入内存和日志文件后即马上返回。
可是,数据始终在内存和日志中是不妥当的,首先内存毕竟是有限的稀缺资源。持续的写入会造成内存的溢出,而日志的写入仅是因为内存数据系统宕机或进程退出后立马消失而採取的一种保护性措施,而不是作为终于的数据持久化。
日志文件不能用来做终于持久化的另外一个原因,就是写入日志时不过简单的追加(append)。读数据时效率会很很的低。
MemStore的flush就是为了解决上述问题而採取的一种有效措施。关于B+树、LSM模型,读者可自行补脑。本文只阐述HRegion上MemStore的flsuh流程,而关于何时发生flush等其它内容将在其它的博文中进行分析。
说了这么多,以下,就開始奇妙的源代码分析之旅吧~
先从宏观上对HRegion上fMemStore的flush流程有一个总体的把握。
HRegion上flush的入口方法为flushCache(),其处理总体流程如图所看到的:
以下,我们看下HRegion上flushCache()方法,代码例如以下:
/**
* Flush the cache.
*
* When this method is called the cache will be flushed unless:
* <ol>
* <li>the cache is empty</li>
* <li>the region is closed.</li>
* <li>a flush is already in progress</li>
* <li>writes are disabled</li>
* </ol>
*
* <p>This method may block for some time, so it should not be called from a
* time-sensitive thread.
* 这种方法可能会堵塞一段时间,所以对时间敏感的线程不应该调用该方法。
*
* @return true if the region needs compacting
*
* @throws IOException general io exceptions
* @throws DroppedSnapshotException Thrown when replay of wal is required
* because a Snapshot was not properly persisted. The region is put in closing mode, and the
* caller MUST abort after this.
*/
public FlushResult flushcache() throws IOException {
// fail-fast instead of waiting on the lock
// 高速失败,而不是等待锁
// 假设Region正处于关闭状态。记录日志,并返回CANNOT_FLUSH的刷新结果
if (this.closing.get()) {
String msg = "Skipping flush on " + this + " because closing";
LOG.debug(msg);
return new FlushResult(FlushResult.Result.CANNOT_FLUSH, msg);
} // 获取任务追踪器。并创建初始状态
MonitoredTask status = TaskMonitor.get().createStatus("Flushing " + this); // 设置任务追踪器的状态:请求Region读锁
status.setStatus("Acquiring readlock on region"); // block waiting for the lock for flushing cache
// 获取Region的读锁,堵塞等待刷新缓存的锁释放 /**
* 我的理解,这个lock锁好像是Region行为上的一个读写锁,加上这个锁,控制Region的总体行为,比方flush、compact、close等。
* flush和compact使用的是读锁,是一个共享锁,意味着flush和compact能够同步进行。可是不能运行close。由于close是写锁。
* 它是一个独占锁。一旦它占用锁。其它线程就不能发起flush、compact等操作,当然,close线程本身除外,由于Region在下线前要保证
* MemStore内的数据被flush到文件。
*/
lock.readLock().lock();
try {
// 假设Region已经下线。记录日志并返回CANNOT_FLUSH的结果
if (this.closed.get()) {
String msg = "Skipping flush on " + this + " because closed";
LOG.debug(msg);
status.abort(msg);
return new FlushResult(FlushResult.Result.CANNOT_FLUSH, msg);
} // 假设协处理器不为空
if (coprocessorHost != null) {
// 设置任务追踪器的状态:运行协处理器预刷写钩子preFlush()方法 status.setStatus("Running coprocessor pre-flush hooks");
// 运行协处理器预刷写钩子preFlush()方法
coprocessorHost.preFlush();
}
if (numMutationsWithoutWAL.get() > 0) {
numMutationsWithoutWAL.set(0);
dataInMemoryWithoutWAL.set(0);
}
synchronized (writestate) {
if (!writestate.flushing && writestate.writesEnabled) {
// 假设writestate不是flushing,且writestate的能够读取启用,将状态中的flushing设置为true。表示正在刷新
this.writestate.flushing = true;
} else { // 否则记录日志。并返回CANNOT_FLUSH的结果
if (LOG.isDebugEnabled()) {
LOG.debug("NOT flushing memstore for region " + this
+ ", flushing=" + writestate.flushing + ", writesEnabled="
+ writestate.writesEnabled);
}
String msg = "Not flushing since "
+ (writestate.flushing ? "already flushing"
: "writes not enabled");
status.abort(msg);
return new FlushResult(FlushResult.Result.CANNOT_FLUSH, msg);
}
}
try {
// 运行真正的flush
FlushResult fs = internalFlushcache(status); // 刷新结束后。假设协处理器不为空,运行协处理器的钩子方法postFlush()
if (coprocessorHost != null) {
status.setStatus("Running post-flush coprocessor hooks");
coprocessorHost.postFlush();
} // 状态追踪器标记完毕状态
status.markComplete("Flush successful");
// 返回刷新结果
return fs;
} finally {
synchronized (writestate) { // 将writestate中的flushing、flushRequested均设置为false
writestate.flushing = false;
this.writestate.flushRequested = false;
writestate.notifyAll();
}
}
} finally {
// 释放读锁
lock.readLock().unlock();
// 清空状态
status.cleanup();
}
}
通过代码我们能够知道。其处理逻辑例如以下:
1、首先须要推断下HRegion的状态,假设Region正处于关闭状态,记录日志,并返回CANNOT_FLUSH的刷新结果。
ps:这是大数据诸多框架,比方HDFS、HBase、Spark等绝大多数内部处理流程採取的一种通用的模式。推断涉及到的实体,比方HRegion、DataNode、DataXceiveServer等的状态,比方正在关闭closing、已经关闭closed等,目的是协调各实体协同工作,保障本处理流程是真实有效的。
2、获取任务追踪器,并创建初始状态:Flushing ****HRegion,初始化后的状态对象为MonitoredTask类型的status;
3、设置任务追踪器的状态:请求Region读锁:Acquiring readlock on region;
4、获取Region的读锁,堵塞等待刷新缓存的锁释放;
5、再次推断HRegion的状态。假设Region已经下线,记录日志并返回CANNOT_FLUSH的结果。
6、假设协处理器不为空:
6.1、设置任务追踪器的状态:运行协处理器预刷写钩子preFlush()方法:Running coprocessor pre-flush hooks;
6.2、运行协处理器预刷写钩子preFlush()方法;
7、假设writestate不是flushing。且writestate的能够读取启用,将状态中的flushing设置为true,表示正在刷新,否则记录日志,并返回CANNOT_FLUSH的结果。
8、调用internalFlushcache()方法。运行真正的flush。
9、刷新结束后,假设协处理器不为空,设置状态,即Running post-flush coprocessor hooks。并运行协处理器的钩子方法postFlush();
10、状态追踪器标记完毕状态:Flush successful。
11、将writestate中的flushing、flushRequested均设置为false;
12、释放读锁。并清空状态追踪器的状态。
13、返回刷新结果。
至此,HRegion上MemStore的flush流程所有完成,当中internalFlushcache()是其真正运行flush的核心方法。关于这部分我们将在下一篇文章中解说。在此,仅仅是概括下外围的总体流程。
关于上述流程,有下面几点须要单独说明下:
1、关于closing和closed标志位状态的推断
HRegion中,有两个关于关闭的状态标志位成员变量,分别定义例如以下:
final AtomicBoolean closed = new AtomicBoolean(false);
final AtomicBoolean closing = new AtomicBoolean(false);
为什么须要两个状态标志位呢?我么知道,Region下线关闭时,须要处理一些诸如flush等的操作,所以一般比較耗时。那么在其下线关闭期间,我们不希望该Region再运行flsuh、compact等请求,所以,我们就须要两个标志位,一个表示正在关闭过程的closing。另外一个是已经关闭的closed。
所以。flush、compact等流程的运行,都会去推断这两个状态位,确保flush和compact同意被运行。
2、关于读写锁的使用
HRegion中,保持了两把锁,分别定义例如以下:
// Used to guard closes 用于保护关闭
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// Stop updates lock 停止更新锁
private final ReentrantReadWriteLock updatesLock = new ReentrantReadWriteLock();
这两个锁均为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的写锁。
3、关于写状态WriteState的使用
再来说下HRegion的另外一个成员变量writestate,它是HRegion的内部类WriteState类型的,这个类是一种协调刷新、合并与关闭操作的很使用的数据结构。它的关键成员变量定义例如以下:
// Set while a memstore flush is happening.
// 当一个memstore刷新发生时设置
volatile boolean flushing = false;
// Set when a flush has been requested.
// 当一个刷新请求发生时设置
volatile boolean flushRequested = false;
// Number of compactions running.
// 合并进行的数目
volatile int compacting = 0;
// Gets set in close. If set, cannot compact or flush again.
// 假设被设置,将不再支持合并与刷新
volatile boolean writesEnabled = true;
// Set if region is read-only
// 假设Region仅仅读时设置
volatile boolean readOnly = false;
// whether the reads are enabled. This is different than readOnly, because readOnly is
// static in the lifetime of the region, while readsEnabled is dynamic
// 读取是否启用。这是不同于仅仅读的,由于仅仅读是一生静态的,而readsEnabled是动态的
volatile boolean readsEnabled = true;
当中。当一个flush发生或者正在进行时。flushing会被设置为true。而当一个flush请求发生时,flushRequested被设置为true。
另外,还包括了合并进行的数目compatcing、可写状态writesEnabled、可读状态readsEnabled和仅仅读状态readOnly等。
为什么要用这么一个数据结构来表示Region的状态呢?我们知道,HRegion代表了HBase表中按行切分的区域。在HRegion上,可能存在flush、compact等多种操作。使用单一的操作并不能非常好的表达出HRegion的状态。所以作者构思出这么一个数据结构,协调fulsh、compact及其HRegion读写等状态。
好了,期待下一篇关于flush核心流程的介绍吧!
HBase源代码分析之HRegion上MemStore的flsuh流程(一)的更多相关文章
- HBase源代码分析之HRegion上MemStore的flsuh流程(二)
继上篇<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>之后.我们继续分析下HRegion上MemStore flush的核心方法internalFlushc ...
- HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)
在<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>.<HBase源代码分析之HRegion上MemStore的flsuh流程(二)>等文中.我们 ...
- HBase源代码分析之HRegionServer上MemStore的flush处理流程(二)
继上篇文章<HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)>遗留的问题之后,本文我们接着研究HRegionServer上MemStore的fl ...
- HBase源代码分析之MemStore的flush发起时机、推断条件等详情
前面的几篇文章.我们具体介绍了HBase中HRegion上MemStore的flsuh流程,以及HRegionServer上MemStore的flush处理流程.那么,flush究竟是在什么情况下触发 ...
- HBase源代码分析之MemStore的flush发起时机、推断条件等详情(二)
在<HBase源代码分析之MemStore的flush发起时机.推断条件等详情>一文中,我们具体介绍了MemStore flush的发起时机.推断条件等详情.主要是两类操作.一是会引起Me ...
- Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)
本文继<Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)>,接着讲述MapReduce作业在MRAppMaster上处理总流程,继上篇讲到作业初始化之后的作 ...
- Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)
我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMas ...
- HBase源代码分析
http://www.docin.com/p-647062205.html http://blog.csdn.net/luyee2010/article/category/1285622 http:/ ...
- 【Nutch2.2.1源代码分析之5】索引的基本流程
一.各个主要类之间的关系 SolrIndexerJob extends IndexerJob 1.IndexerJob:主要完成 2.SolrIndexerJob:主要完成 3.IndexUtil:主 ...
随机推荐
- display:table 表格布局
table 布局最大的特点 1.同行等高 2.宽度自动调节 那么table-cell是不是具备这个特点呢?答案是yes,为什么呢?css中有一个有意思的规则“创建匿名表格元素”. 拿table ...
- Cisco VPN Client Win10无法使用的解决办法
http://files.cnblogs.com/files/Flyear/VPN_Win10_ByDuke.zip 1. 关闭系统所有窗口,控制面板一定要关闭. 2. 运行winfix.exe, 按 ...
- vue-cli 脚手架目录结构说明
目录结构截图如下 /build 编译配置文件目录,由脚手架自动生成 /config webpack 配置文件目录,由脚手架自动生成 /node_modules node依赖目录,可通过package. ...
- Python把给定的列表转化成二叉树
在LeetCode上做题时,有很多二叉树相关题目的测试数据是用列表给出的,提交的时候有时会出现一些数据通不过,这就需要在本地调试,因此需要使用列表来构建二叉树,方便自己调试.LeetCode上二叉树结 ...
- c#中获取路径方法
要在c#中获取路径有好多方法,一般常用的有以下五种: //获取应用程序的当前工作目录. String path1 = System.IO.Directory.GetCurrentDirectory() ...
- 使用dynamic特性处理XML文档
处理XML文档是我们经常需要进行的一项工作,尤其是在进行网络服务相关编程时,比如更新RSS等.在.NET 3.5中引入了Linq To XML,使得XML文档的读写已经大大简化,而.NET 4.0中最 ...
- 用C#实现DES加密解密解决URL参数明文的问题
啥也不说,直接上代码. 加密解码,封装到一个类,key可以自己修改. using System; using System.Security.Cryptography; using System.Te ...
- R语言高性能编程(二)
接着上一篇 一.减少内存使用的简单方法1.重用对象而不多占用内存 y <- x 是指新变量y指向包含X的那个内存块,只有当y被修改时才会复制到新的内存块,一般来说只要向量没有被其他对象引用,就可 ...
- 【转】MYSQL 使用SQLyog导入遇到问题解决
原文地址:http://blog.163.com/o5655@126/blog/static/1667428342010910112510738/ 昨天公司想要将一个数据库的数据导出再导入到另外一个 ...
- Composer使用笔记
安装 1.windows中安装Composer 一般来说,windows下安装composer有两种办法,一种是直接下载并运行Composer-Setup.exe,这种方法在中国似乎很难完成安装.另一 ...