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 类中:

  1. applyFamilyMapToMemstore 传入参数包含 WriteEntry 对象,如果该对象为 null,则调用 beginMemstoreInsert() 会创建一个 WriterEntry 且已加入 writeQueue。

  2. doMiniBatchMutation 批量修改,put 或者 delete。通过 beginMemstoreInsert() 获取一个包含最新的 WriteNumber 的 WriteEntry

  3. internalFlushcache flush 的实现方法,获取最新的 writeNumber

  4. 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

从变量到方法都有了明显的变化,下一篇专门比较下。

参考

  1. HBase中MVCC的实现机制及应用情况

HBase MVCC 代码阅读(一)的更多相关文章

  1. 代码阅读分析工具Understand 2.0试用

    Understand 2.0是一款源代码阅读分析软件,功能强大.试用过一段时间后,感觉相当不错,确实可以大大提高代码阅读效率.由于Understand功能十分强大,本文不可能详尽地介绍它的所有功能,所 ...

  2. Android 上的代码阅读器 CoderBrowserHD 修改支持 go 语言代码

    我在Android上的代码阅读器用的是 https://github.com/zerob13/CoderBrowserHD 改造的版本,改造后的版本我放在 https://github.com/ghj ...

  3. Linux协议栈代码阅读笔记(二)网络接口的配置

    Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...

  4. [置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  5. 图形化代码阅读工具——Scitools Understand

    Scitools出品的Understand 2.0.用了很多年了,比Source Insight强大很多.以前的名字叫Understand for C/C++,Understand for Java, ...

  6. Python - 关于代码阅读的一些建议

    初始能力 让阅读思路保持清晰连贯,主力关注在流程架构和逻辑实现上,不被语法.技巧和业务流程等频繁地阻碍和打断. 建议基本满足以下条件,再开始进行代码阅读: 具备一定的语言基础:熟悉基础语法,常用的函数 ...

  7. MediaInfo代码阅读

      MediaInfo是一个用来分析媒体文件的开源工具. 支持的文件非常全面,基本上支持所有的媒体文件. 最近是在做HEVC开发,所以比较关注MediaInfo中关于HEVC的分析与处理. 从Meid ...

  8. Tools - 一些代码阅读的方法

    1 初始能力 让阅读思路清晰连贯,保持在程序的流程架构和逻辑实现上,不被语法.编程技巧和业务流程等频繁地阻碍和打断. 语言基础:熟悉基础语法,常用的函数.库.编程技巧等: 了解设计模式.构建工具.代码 ...

  9. Bleve代码阅读(二)——Index Mapping

    引言 Bleve是Golang实现的一个全文检索库,类似Lucene之于Java.在这里通过阅读其代码,来学习如何使用及定制检索功能.也是为了通过阅读代码,学习在具体环境下Golang的一些使用方式. ...

随机推荐

  1. SqlServer发送邮件,定时作业

    今天偶然研究了一下sqlserver发送邮件的功能,之前听说过可以发,但是一直没尝试过,只是用C#写后台程序的方式来发邮件. 现在又多了一种发送邮件的途径. 大致的步骤如下: 1.配置sqlserve ...

  2. 【SSH三个框架】Hibernate第七基金会:不少下属业务

    相对于上述一关系,在这里,下一个一对多关系说明. 另外,在上述.我们描述了许多人描述的一一对应关系.在关系数据库是多对一的关系,但也有许多关系.但,只知道它是不够的,Hibernate它是一种面向对象 ...

  3. jquery扩展

    jQuery插件的开发包括两种: 一种是类级别的插件开发,即给jQuery添加新的全局函数,相当于给jQuery类本身添加方法.jQuery的全局函数就是属于jQuery命名空间的函数,另一种是对象级 ...

  4. 【Android接口实现】PhotoView——单点支持/多图像缩放,实现了触摸

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 今天给大家介绍的开源项目,是来自Github的PhotoView项目,这个项目的主要功能是实现普通的Imag ...

  5. HTML5实现图片文件异步上传

    原文:HTML5实现图片文件异步上传 利用HTML5的新特点做文件异步上传非常简单方便,本文主要展示JS部分,html结构.下面的代码并未使用第三发库,如果有参照,请注意一些未展现出来的代码片段.我这 ...

  6. XEvent – SQL Server Log文件对磁盘的写操作大小是多少

    原文:XEvent – SQL Server Log文件对磁盘的写操作大小是多少 本篇是上一篇SQL Server Log文件对磁盘的写操作大小是多少的续,使用XEvent收集SQL Server D ...

  7. CSharp设计模式读书笔记(9):组合模式(学习难度:★★★☆☆,使用频率:★★★★☆)

    组合模式(Composite Pattern): 组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构.组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可 ...

  8. iOS8自适应布局视频教程

    联系:http://www.elsyy.com/course/6480 这是颐和园最近录制iOS8视频课程.简介iOS8出现在自适应布局. 本教程的书,颐和园<ios8 swift编程指南> ...

  9. MVC使用百度开源文本编辑器UEditor实现图文并茂,字数限制,上传图片或涂鸦

    原文:MVC使用百度开源文本编辑器UEditor实现图文并茂,字数限制,上传图片或涂鸦 文本编辑器有很多,比如ticymce和CKEditor就比较好用,但涉及到图片.文件上传,需要结合CKFinde ...

  10. Java多线程的~~~Lock接口和ReentrantLock使用

    在多线程开发.除了synchronized这个keyword外,我们还通过Lock接口来实现这样的效果.由Lock接口来实现 这样的多线程加锁效果的优点是非常的灵活,我们不在须要对整个函数加锁,并且能 ...