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:主 ...
随机推荐
- jQuery选择器(ID选择器)第一节
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/stri ...
- 全国DNS服务器地址(部分)
广东: 广东省广州市(中国电信) 首选DNS:61.144.56.100 备份DNS:61.144.56.101 广东省广州市越秀区(中国电信) 首选DNS:202.96.128.86 备份DNS:2 ...
- Socket 的理解及实例
Socket 的理解及实例Socket 的理解TCP/IP要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Intern ...
- selenium_Alert
网页测试,最避免不了的就是弹出框,但是弹出框你真的分的清吗? Alert prompt comfirm 先来认识一下这三个弹窗 代码如下 <!DOCTYPE html> <html ...
- asp .net连接打开数据库初步
1 #endregion59 WebDriver
- Hi,腾讯WeTest联合Unity官方打造的性能分析工具UPA,今日全新发布!
早在2016年ChinaJoy开始,WeTest曾受邀出席过Unity中国的线下性能场的活动,介绍我们的自动化框架和王者荣耀的故事.当时的活动很成功,期间我们收到了不少Unity开发者的好评,也为我们 ...
- 使用mysql5.7新特性(虚拟列)解决使用前通配符性能问题
众所周知,在mysql里的后通配符可以使用索引查找,前通配查询却无法使用到索引,即使是使用到了索引,也是使用了索引全扫描,效率依然不高,再MySQL5.7之前,一直都没有好的办法解决,但是到了MySQ ...
- Ionic3学习笔记(十一)实现省市区三级联动
本文为原创文章,转载请标明出处 目录 安装 ion-multi-picker 导入 app.module.ts 创建 provider 创建 page 一个坑 更多 效果图 1. 安装 ion-mul ...
- 《Java并发编程》之线程中断与终止线程运行
Java中启动一个线程很容易,通常情况下我们都是等到任务运行结束后让线程自行停止.但有时需要在任务正在运行时取消他们,使得线程快速结束.对此Java并没有提供任何机制.但是我们可以通过Java提供的线 ...
- 微信小程序与Java后台通信
一.写在前面 最近接触了小程序的开发,后端选择Java,因为小程序的代码运行在腾讯的服务器上,而我们自己编写的Java代码运行在我们自己部署的服务器上,所以一开始不是很明白小程序如何与后台进行通信的, ...