了解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流程(一)的更多相关文章

  1. HBase源代码分析之HRegion上MemStore的flsuh流程(二)

    继上篇<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>之后.我们继续分析下HRegion上MemStore flush的核心方法internalFlushc ...

  2. HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)

    在<HBase源代码分析之HRegion上MemStore的flsuh流程(一)>.<HBase源代码分析之HRegion上MemStore的flsuh流程(二)>等文中.我们 ...

  3. HBase源代码分析之HRegionServer上MemStore的flush处理流程(二)

    继上篇文章<HBase源代码分析之HRegionServer上MemStore的flush处理流程(一)>遗留的问题之后,本文我们接着研究HRegionServer上MemStore的fl ...

  4. HBase源代码分析之MemStore的flush发起时机、推断条件等详情

    前面的几篇文章.我们具体介绍了HBase中HRegion上MemStore的flsuh流程,以及HRegionServer上MemStore的flush处理流程.那么,flush究竟是在什么情况下触发 ...

  5. HBase源代码分析之MemStore的flush发起时机、推断条件等详情(二)

    在<HBase源代码分析之MemStore的flush发起时机.推断条件等详情>一文中,我们具体介绍了MemStore flush的发起时机.推断条件等详情.主要是两类操作.一是会引起Me ...

  6. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(二)

    本文继<Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)>,接着讲述MapReduce作业在MRAppMaster上处理总流程,继上篇讲到作业初始化之后的作 ...

  7. Yarn源码分析之MRAppMaster上MapReduce作业处理总流程(一)

    我们知道,如果想要在Yarn上运行MapReduce作业,仅需实现一个ApplicationMaster组件即可,而MRAppMaster正是MapReduce在Yarn上ApplicationMas ...

  8. HBase源代码分析

    http://www.docin.com/p-647062205.html http://blog.csdn.net/luyee2010/article/category/1285622 http:/ ...

  9. 【Nutch2.2.1源代码分析之5】索引的基本流程

    一.各个主要类之间的关系 SolrIndexerJob extends IndexerJob 1.IndexerJob:主要完成 2.SolrIndexerJob:主要完成 3.IndexUtil:主 ...

随机推荐

  1. HTTPS协议,TLS协议

    一.HTTPS 协议 HTTPS协议其实就是HTTP over TSL,TSL(Transport Layer Security) 传输层安全协议是https协议的核心. TSL可以理解为SSL (S ...

  2. WebService--jax-spring集成

    如果使用javax.jws内容编写webservice,则只能通过将程序打成jar包的形式运行,如果要想通过web容器进行发布,则需要使用其他webservice框架.下面介绍jaxws与spring ...

  3. 在昆明网络SEO的走向站外的优化该何去何从?

    昨天大概讲了SEO的站内优化,今天我们来讲讲网站站外的优化. 站外主要以第三平台为主,其中包含站外推广:常规推广.外链建设:利用第三方平台优化关键词排名: 1.博客平台,现在有好多博客平台是很不错的, ...

  4. 三元运算符2>1?true:false;

    1.说明: xxx?xxx:xxx; 第一个'xxx'是写条件语句,条件自己根据需求定 第二个'xxx'是当条件为真时会得到的值 第三个'xxx'是当条件为假时会得到的值 2.例子: 代码: bool ...

  5. php多个文件上传

    表单如下 <form class="form-horizontal" action="{:U('System/addAdvert')}" method=& ...

  6. Docker Swarm 中最重要的概念- 每天5分钟玩转 Docker 容器技术(94)

    从主机的层面来看,Docker Swarm 管理的是 Docker Host 集群.所以先来讨论一个重要的概念 - 集群化(Clustering). 服务器集群由一组网络上相互连接的服务器组成,它们一 ...

  7. 工作中用到的一些shell命令

    1.将十进制转换为十六进制 for i in `seq 0 127`; do printf "%02x\n" $i; done

  8. python数据结构与算法篇:排序

    1.冒泡排序(英语:Bubble Sort) 它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成. ...

  9. 延迟执行之 Invoke 函数

    Invoke 函数需要继承 MonoBehaviour 类后才能使用. Invoke(string str,float a):a 秒后执行名为 str 函数(只会调用一次). Invoke(strin ...

  10. 使用语句清除sqlserver数据库日志文件

    修改其中的3个参数(数据库名,日志文件名,和目标日志文件的大小),运行即可 SET NOCOUNT ON DECLARE @LogicalFileName sysname, @MaxMinutes I ...