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:主 ...
随机推荐
- MyEclipse下安装FatJar打包工具
方法一:help > software updates > Find and install > add > add remote site name:FatJar url:h ...
- 如何添加title左侧的图标
在titile标签上方插入以下代码,图片要求格式必须是.ico. <link rel="shortcut icon" href="images/favicon.ic ...
- Python待分析的模块
fcntl 文件控制模块 http://docs.python.org/2.7/library/fcntl.html#module-fcntl struct 二进制文本处理模块 http://docs ...
- Spring JDBC 示例
在使用普通的 JDBC 数据库时,就会很麻烦的写不必要的代码来处理异常,打开和关闭数据库连接等.但 Spring JDBC 框架负责所有的低层细节,从开始打开连接,准备和执行 SQL 语句,处理异常, ...
- 完美解决--用VS中的Git做代码管理器,与他人共享代码
1.创建代码仓库,这里说一下为什么要创建仓库,Git不能够作为源代码管理器,vs中自带的也只能够在本地进行管理,要和他们共享的话必须要有服务器端去存储代码,类似于SVN,它就有客户端和服务器端,这里推 ...
- JSF页面中使用js函数回调后台action方法
最近遇到了一个问题就是在JSF页面中嵌入html页面,这个html页面中很多功能是使用js动态生成的,现在需要在js函数里想去调用JSF中action类method()方法并动态传送数据给后台进行处理 ...
- Python第一天自学,变量,基本数据类型
PyCharm 一些简单常用设置操作设置模板 File->Settings->Editor->File and Code Templates //切换python版本File-> ...
- Python 日志处理(三) 日志状态码分析、浏览器分析
在企业中,从日志中提取数据进行分析,可以帮助企业更加了解用户行为,用户最感兴趣的产品或者内容,分析得到数据后,可以决定企业在今后的走向. 从这些日志数据中,比较重要的有: 1. 用户访问最多的url, ...
- Python网络编程篇之select和epoll
1. select 原理 在多路复⽤的模型中, ⽐较常⽤的有select模型和epoll模型. 这两个都是系统接⼝, 由操作系统提供. 当然, Python的select模块进⾏了更⾼级的封装. ⽹络 ...
- [贪心][高精]P1080 国王游戏(整合)
题目描述 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整数,国王自己也在左.右手上各写一个整数.然后,让这 n 位大臣排成一排,国王站在队伍的最 ...