HBase MVCC 代码阅读(一)
MultiVersionConcurrencyControl.java,版本 0.94.1
MultiVersionConsistencyControl 管理 memstore 中的读写一致性。该类实现了一种机制,达到如下的目的:
- 提供接口让 reader 知道可以忽略哪些元素项
- 提供一个新的 WriteNumber 给 writer
- 将写更新提供给 reader(通过原子事务)
1 变量
主要包含两个变量:
- memstoreRead 前面文章中提到的 ReadPoint
- memstoreWrite 前面文章中提到的 WriteNumber
变量描述private volatile long
,私有,长整形,volatile 标记,线程间可见。
readWriters 私有 object 常量,加锁时候用。
writeQueue 写操作队列,默认构造函数初始化的LinkedList,元素类型为 WriterEntry。注意,LinkedList 这个双向链表数据结构,适合随机增、删,但不是线程安全的。
perThreadReadPoint 线程局部变量,保存当前线程获取的 readPoint,static final
,ThreadLocal,用 Long.MAX_VALUE初始化。
FIXED_SIZE 一个 MVCC 对象占用内存空间的大小,包括:一个锁对象 readWriters,两个 long 型变量 memstoreRead 和 memstoreWrite,两个应用对象的地址:writeQueue 和 perThreadReadPoint。
2 内嵌类
WriteEntry 每个 writer 持有一个,保存 writeNumber,并用一个 completed 标记是否写完。
实际上就是对理论中 writeNumber 的包装:通过 writeNumber 对应到一个writer,通过 completed 标记 writer 是否写完。
3 方法
3.1 构造方法
public MultiVersionConsistencyControl();
唯一的一个构造方法,无参数,将 memstoreRead 和 memstoreWrite 初始化为0。
两个变量的值一般不会为0,后续会用初始化方法,将两个变量的值设置为一个正常值。
3.2 初始化方法
public void initialize(long startPoint);
初始化 memstoreRead 和 memstoreWrite 。
在初始化的时候,会用 synchronized 对 writeQueue 加锁。
判断 memstoreWrite 是否和 memstoreRead 相同,如果两者不同,认为这个是已经用过的 MVCC 对象,不需要再做初始化;如果相同,则将 memstoreRead 和 memstoreWriet 设置为输入参数。
initialize 方法唯一一处引用在 org.apache.hadoop.hbase.regionserver.HRegion.initializeRegionInternals
方法中。
HRegion 包含一个私有的常量 mvcc ,对象声明的时候用 MVCC 的缺省构造方法初始化。HRegion 的 org.apache.hadoop.hbase.regionserver.HRegion.initialize
对 HRegion 初始化的时候,调用了 initialiazeRegionInterals
,初始化 Region 的内部对象,把 mvcc 初始化为一个合理的值。
所以,上面 MVCC 初始化方法的判断逻辑是 memstoreRead == memstoreWrite
时候才有必要进行初始化。
3.3 memstoreRead相关
在实现的时候,ReadPoint 包含两类,一个是全局的,由 memstoreRead 记录的,这个是所有线程均可见、可改的,一个是局部的, ThreadLocal 的 readPoint,每个线程有一个自己的副本,且这个不保证与全局的 readPoint 一样。在代码实现的时候,需要对这两类 readPoint 小心处理。
3.3.1 memstoreReadPoint
public long memstoreReadPoint();
返回当前 MVCC 记录的 memstoreRead。在两处被用到:
一处是 MVCC 的 resetThreadReadPoint,用 mvcc 的全局 readpoint 更新线程局部的 readPoint。
一处是在 HRegion 类中,getSmallestReadPoint 方法,比较该 Region 上所有 scanner 持有的 readPoint,以及 Region MVCC 对象的 readPoint,在这所有中找到一个最小的。
3.3.2 ThreadReadPoint
ThreadReadPoint 相关4个方法,均标记为 static 。
public static long getThreadReadPoint();
获取线程局部 readPoint,主要由 memstore scanner 调用,以确认跳过哪些值。
分别在 org.apahce.hadoop.hbase.regionserver.MemStore.MemStoreScanner.getNext
方法和 org.apahce.hadoop.hbase.regionserver.StoreFileScanner.skipKVsNewerThanReadpoint
方法中被调用。
在 getNext
方法中,获取线程当前的 readPoint,scanner 在扫描的时候,所有比 readPonit 老的值才可以被读到。
在 skipKVsNewerThanReadpoint
方法中则想法,获取线程当前的 readPoint 后,把所有比 readPoint 新的 KV 对象会被跳过。
public static void setThreadReadPoint(long readPoint);
设置线程局部 readPoint。
主要在两个地方用到:
org.apahce.hadoop.hbase.regionserver.HRegion.RegionScannerImpl
配合对象自己的 readPt 使用。
构造方法,根据隔离级别参数,如果为 READ_UNCOMMITED,将线程局部 readPoint 设置为 long.MAX_VALUE。
next()
方法, 带 synchronized 标记,在开始取数据之前,先用 readPt 更新线程局部 readPoint,然后再取值。
reseek()
方法,带 synchronized 标记,重新定位之前,用 readPt 更新线程局部的 readPoint 标记。
org.apahce.hadoop.hbase.regionserver.Store
compactStore
方法。如前面理论描述中提到的,先获取当前 region 所有读者中持有的最老的 readPoint,用 setThreadReadPoint
方法更新线程局部 readPoint。后续做 compact,以这个smallestReadPoint 为基准。
由上可见,线程局部维护的 ThreadReadPoint 不自动更新,可以随时被读取,但是在所有写操作之前,需要对线程局部的 ThreadReadPoint 更新,写入一个确定的值,以确认最新。
public static void resetThreadReadPoint();
将线程局部 readPoint 重置为0。Find Usages 工具发现该方法实际未被使用。
public static long resetThreadReadPoint(MultiVersionConsistencyControl mvcc);
用一个 MVCC 对象的 memstoreReadPoint 更新当前线程的局部 readPoint,并返回更新后的局部 readPoint。
3.4 memstoreWrite相关
涉及4个方法,入口是 beginMemstoreInsert()
和 completeMemstoreInsert()
,completeMemstoreInsert()
在实现的时候,先调用 advanceMemstore()
后调用 waitForRead()
,以下依次说明。
public WriteEntry beginMemstoreInsert();
获取一个最新的 writerNumber,核心就是干这一件事。
对 writeQueue 加锁,获取一个新的 memstoreWrite,利用新的 memstore 新建个 WriteEntry 对象,并添加到 writeQueue 链表中,最后将新建的 WriteEntry 对象返回。
该方法的调用都在 HRegion 类中:
applyFamilyMapToMemstore 传入参数包含 WriteEntry 对象,如果该对象为 null,则调用
beginMemstoreInsert()
会创建一个 WriterEntry 且已加入 writeQueue。doMiniBatchMutation 批量修改,put 或者 delete。通过
beginMemstoreInsert()
获取一个包含最新的 WriteNumber 的 WriteEntryinternalFlushcache flush 的实现方法,获取最新的 writeNumber
mutateRowsWithLocks region 内的原子修改,获取最新的 writeNumber
public void completeMemstoreInsert(WriteEntry e);
按序调用后面两个方法,没有其他。
boolean advanceMemstore(WriteEntry e);
"从头开始遍历writeQueue,移除所有已完成的WriteEntry对象,最后将memstoreRead更新为最新已完成的memstoreWrite;"
boolean advanceMemstore(WriteEntry e) {
synchronized (writeQueue) {
e.markCompleted();
long nextReadValue = -1;
boolean ranOnce=false;
while (!writeQueue.isEmpty()) {
ranOnce=true;
WriteEntry queueFirst = writeQueue.getFirst();
if (nextReadValue > 0) {
if (nextReadValue+1 != queueFirst.getWriteNumber()) {
throw new RuntimeException("invariant in completeMemstoreInsert violated, prev: "
+ nextReadValue + " next: " + queueFirst.getWriteNumber());
}
}
if (queueFirst.isCompleted()) {
nextReadValue = queueFirst.getWriteNumber();
writeQueue.removeFirst();
} else {
break;
}
}
if (!ranOnce) {
throw new RuntimeException("never was a first");
}
if (nextReadValue > 0) {
synchronized (readWaiters) {
memstoreRead = nextReadValue;
readWaiters.notifyAll();
}
}
if (memstoreRead >= e.getWriteNumber()) {
return true;
}
return false;
}
}
public void waitForRead(WriteEntry e);
等待全局的 readPoint 更新到当前 writer 的事务号。"阻塞当前线程,直到memstoreRead等于当前WriteEntry的memstoreWrite,至此表明当前WriteEntry之前的所有更新事务都已经完成"
正如之前 解释 HBase MVCC 实现原理的时候提到的,所有事务号小于 readPoint 的事务,被认为是"确定安全"了,相关的线程也就可以被释放了。
public void waitForRead(WriteEntry e) {
boolean interrupted = false;
synchronized (readWaiters) {
while (memstoreRead < e.getWriteNumber()) {
try {
readWaiters.wait(0);
} catch (InterruptedException ie) {
// We were interrupted... finish the loop -- i.e. cleanup --and then
// on our way out, reset the interrupt flag.
interrupted = true;
}
}
}
if (interrupted) Thread.currentThread().interrupt();
}
4 最新版本
MVCC 最新版本的代码参见:MultiVersionConcurrencyControl.java
从变量到方法都有了明显的变化,下一篇专门比较下。
参考
HBase MVCC 代码阅读(一)的更多相关文章
- 代码阅读分析工具Understand 2.0试用
Understand 2.0是一款源代码阅读分析软件,功能强大.试用过一段时间后,感觉相当不错,确实可以大大提高代码阅读效率.由于Understand功能十分强大,本文不可能详尽地介绍它的所有功能,所 ...
- Android 上的代码阅读器 CoderBrowserHD 修改支持 go 语言代码
我在Android上的代码阅读器用的是 https://github.com/zerob13/CoderBrowserHD 改造的版本,改造后的版本我放在 https://github.com/ghj ...
- Linux协议栈代码阅读笔记(二)网络接口的配置
Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...
- [置顶] Linux协议栈代码阅读笔记(一)
Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...
- 图形化代码阅读工具——Scitools Understand
Scitools出品的Understand 2.0.用了很多年了,比Source Insight强大很多.以前的名字叫Understand for C/C++,Understand for Java, ...
- Python - 关于代码阅读的一些建议
初始能力 让阅读思路保持清晰连贯,主力关注在流程架构和逻辑实现上,不被语法.技巧和业务流程等频繁地阻碍和打断. 建议基本满足以下条件,再开始进行代码阅读: 具备一定的语言基础:熟悉基础语法,常用的函数 ...
- MediaInfo代码阅读
MediaInfo是一个用来分析媒体文件的开源工具. 支持的文件非常全面,基本上支持所有的媒体文件. 最近是在做HEVC开发,所以比较关注MediaInfo中关于HEVC的分析与处理. 从Meid ...
- Tools - 一些代码阅读的方法
1 初始能力 让阅读思路清晰连贯,保持在程序的流程架构和逻辑实现上,不被语法.编程技巧和业务流程等频繁地阻碍和打断. 语言基础:熟悉基础语法,常用的函数.库.编程技巧等: 了解设计模式.构建工具.代码 ...
- Bleve代码阅读(二)——Index Mapping
引言 Bleve是Golang实现的一个全文检索库,类似Lucene之于Java.在这里通过阅读其代码,来学习如何使用及定制检索功能.也是为了通过阅读代码,学习在具体环境下Golang的一些使用方式. ...
随机推荐
- hdu 4115 石头剪子布(2-sat问题)
/* 意甲冠军:石头剪子布,目前已知n周围bob会有什么,对alice限制.供u,v,w:设w=0说明a,b回合必须出的一样 否则,必须不一样.alice假设输一回合就输了,否则就赢了 解: 2-sa ...
- DropDownListFor使用ViewData进行绑定的示例
特别注意,经实践: 此方法的ViewBag的名称必须和new SelectList()中的最后一个参数,即下拉框的默认值的名称必须相同,如: ViewBag.Title = WebConst.UnSe ...
- 如何为ios酷我音乐盒下载导出的音乐文件(使用Java程序设计)
这个工具已经准备第二版,读者了解编程软件,可以直接使用,请阅读和使用这个场地 http://blog.csdn.net/jzj1993/article/details/44459983 本文所涉及内容 ...
- SQL于DML(数据库操作语言)采用
1.Insert语句: INSERT [INTO] table [(column1, column2, column3, . . .)] VALUES(value1, value2, value3, ...
- java_Eclipse中SVN的安装步骤(两种)和使用方法
若是只要site地址: http://subclipse.tigris.org/update_1.6.x, 下边可以忽略 一.给Eclipse安装SVN,最常见的有两种方式:手动方式和使用安装向导方 ...
- uva757 - Gone Fishing(馋)
题目:uva757 - Gone Fishing(贪心) 题目大意:有N个湖泊仅仅有一条通路将这些湖泊相连. 每一个湖泊都会给最開始5分钟间隔内能够调到的鱼(f).然后给每过5分钟降低的鱼的数量(d) ...
- 64bit Centos6.4编hadoop-2.5.1
64bit Centos6.4编hadoop-2.5.1 1.说明 a) 因为从apache下载下来的tar.gz包是用32 bit编译的,全部假设用Linux 64作为hadoop的 ...
- C# - Recommendations for Abstract Classes vs. Interfaces
The choice of whether to design your functionality as an interface or an abstract class can somet ...
- 泛型委托及委托中所涉及到匿名方法、Lambda表达式
泛型委托及委托中所涉及到匿名方法.Lambda表达式 引言: 最初学习c#时,感觉委托.事件这块很难,其中在学习的过程中还写了一篇学习笔记:委托.事件学习笔记.今天重新温故委托.事件,并且把最近学习到 ...
- Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 1
Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 序章 Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 序章 -- 新建微软实 ...