关键词:MVCC HBase 一致性

本文最好结合源码进行阅读

什么是MVCC ?

MVCC(MultiVersionConsistencyControl , 多版本控制协议),是一种通过数据的多版本来解决读写一致性问题的解决方案。在隔离性级别中,MVCC可以解决“可重复读”的隔离(即除了最后一级别的幻读无法解决,幻读只能事务串行化解决),基本是同一份数据并发条件下保证读写一致性的一个理想方案了。

一般情况下MVCC的一种实现思路是类似乐观锁(OCC,又叫乐观并发控制) 的实现机制。乐观锁适用于写冲突不大的并发场景,先执行写入,检查是否有冲突,若有冲突则回滚重来,否则提交写请求成功。MVCC获取最新的版本进行写操作,如果失败则回滚,成功则会将当前的版本作为可读点;读操作只能读大于或小于当前版本的数据。这里用版本概念可能会有点混淆,通常可能是timestamp或seqID。

对于单行数据,MVCC非常美好;但对于多行数据事务的更新操作就有问题了。MVCC是在最后检查才上锁,所以,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。因此,一般MVCC实际会配合二阶段锁(2PL)去实施。这样做虽然写事务被迫串行化了,但纯读取的事务不受锁影响且能保证最终的读写一致性。

HBase里的MVCC

HBase里虽然利用了版本这个概念做到了Cell层面的Version,HBase应用侧的version一般使用毫秒级时间戳作为版本,基于LSM的数据修改机制也是利用这个version来实现,包括官方一直未解决的同一毫秒内Delete和Put语义顺序问题(参考:https://yangzhe1991.org/blog/2016/06/hbase-versions-delete-limitations/)。其实这个问题就体现了Version机制的好处,以及由于Version定为毫秒级时间戳不够唯一导致的Version机制崩溃的并发问题。

但HBase真正的MVCC实现则是在HRegion中的写操作的实现。HRegion采用的是一次封锁法(示例代码可参见 HRegion.doMiniBatchMutation(BatchOperationInProgress<?> batchOp) )。

封锁步骤:

1. 对当前事务的所有行获取行锁(其中doMiniBatchMutation不会阻塞获取所有行锁,而是获取多少处理多少,然后在外部循环直至所有mutation操作完成;其他则会阻塞等待行锁)

2. 对region级别的updateLock 进行上读锁。updateLock是个读写锁,并发行级写操作的时候上读锁,region级别的写操作(flush,dropMemStore)的时候上写锁全部写操作阻塞。

MVCC的实现类是MultiVersionConsistencyControl,是个Region级别的MVCC控制。当有写操作来时,MVCC会做如下事情:

1. HRegion级别的seqID自增加一,并且当前 writeNo 设为 seqID + 1000000000。 这个大数的意义是防止别的写操作提交时把readNo提高了,导致当前writeNo成为一个可读状态的id,后面会将其设回正常的seqID (1.1.2 这里貌似有个坑)。

2. 把当前的写操作的一个包含seqID的dummy对象 WriteEntry加进队列。

3. 对于实际写操作本身,先写memstore,再写WAL,如果中间失败则回滚,否则则当做成功继续执行。

4. 不管失败成功,当前这个seqID都是不可再用的了,然后MVCC内排队等待处理当前写请求提交。

5. 写请求提交实际上就是把当前HRegion级别的readNo设为队列中已完成的写请求(包括别的线程的写请求)的seqID最大值,表示seqID以下的写请求都处理完了,可读。

1中提到了设回seqID的坑,正常做法可能是在当前线程cache住AtomicLong自增后的新seqID作为唯一id,但是HBase并没有这样做,而是在较后的build WAL代码中好几个地方调用getSequenceID去设到WALKey里。如果这时候有另一个写请求自增了,然后后续没写请求了,此时两个WriteEntry的seqID是一样的。而且写操作的流程看下来,很可能实际的seqID是NO_SEQUENCE_ID=-1。关于这个问题我翻了一下issue没找到比较相关的,倒是由于一些同步自增的performance问题被人改过(确实挺绕的)。于是我特地去看了1.3.1(1.x 最后的版本)的这部分代码,发现变动很大,MVCC的部分调用逻辑还移到了 WALKey里了。

HBase 1.3.1 的 写操作封锁步骤不变(实际写的步骤也略有变化,把非IO型的构建WALEdit操作从MVCC事务中提出,显然使事务更细粒度), MVCC流程:
1. 去掉HRegion的seqID,writePoint和readPoint统一改由MVCC内维护
2. 开始构建WALEdit,其中在FSWALEntry.stampRegionSequenceId() 方法中会自增writePoint,并WALKey.setWriteEntry。
3. 另一边获取WALKey.getWriteEntry是个异步的过程(用了Disruptor做线程间消息通信),get方法会等待直到第一次set完成才获取出writeEntry。注意此时没有了1.1.2的加一个大数的机制。
4. 获取到writePoint后,开始写memstore和同步WAL(即实际的写操作),如果成功则和1.1.2类似,将readPoint设为已完成的最大writePoint,并调用waitForRead校验readPoint >= currentWritePoint。若失败一样要调整readPoint,只是不用校验。

这里有几个改变点:
1. HBase1.1.2会加上1亿来防止并发的readPoint调整大于当前值,实际在1.3的实现方案中没必要。因为在MVCC的队列中,排在前面的写请求没完成,后面的是无法完成属于自己的WriteEntry的complete操作的,也就是说比自己后添加WriteEntry到队列的写请求是不用担心的。因此1.3.1将自增writePoint和添加队列封装了一个原子方法 MultiVersionConcurrencyControl.begin() ,解决了前文所说的1.1.2每个操作相距甚远同步风险较大的问题。
2. HBase1.1.2的MVCC在complete逻辑是等待之前的写操作完成排到自己,原子操作将队头所有completed的WriteEntry移除,并将它们的最大值作为readPoint。HBase1.3.1的逻辑是严格保证写请求顺序,移除队列头completed的WriteEntries并设最后的那个(就是最大值,因为有序)为readPoint。 waitForRead被单独拎出来作为一个方法,用来解决万一因为同步问题readPoint小于当前的writePoint了,则强制阻塞直到恢复正常的MVCC机制为止。目前还没想到不知道是前面什么操作失败的情况下会出现readPoint<writePoint的情况,但是1.3.1的机制显然更加清晰和安全。

HBase MVCC 机制介绍的更多相关文章

  1. HBase MVCC 代码阅读(一)

    MultiVersionConcurrencyControl.java,版本 0.94.1 MultiVersionConsistencyControl 管理 memstore 中的读写一致性.该类实 ...

  2. MySQL多版本并发控制——MVCC机制分析

    MVCC,即多版本并发控制(Multi-Version Concurrency Control)指的是,通过版本链维护一个数据的多个版本,使得读写操作没有冲突,可保证不同事务读写.写读操作并发执行,提 ...

  3. MySQL 学习笔记(二)MVCC 机制

    之前在讲 MySQL 事务隔离性提到过,对于写操作给读操作的影响这种情形下发生的脏读.不可重复读.虚读问题.是通过MVCC 机制来进行解决的,那么MVCC到底是如何实现的,其内部原理是怎样的呢?我们要 ...

  4. iOS 阶段学习第25天笔记(iOS沙盒机制介绍)

    iOS学习(OC语言)知识点整理 一.iOS沙盒机制介绍 1)概念: 每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用放入文件 系统隔离,ios系统不允许访问 其他应用的应用沙盒 ...

  5. iOS沙盒机制介绍,Block 的介绍

    一.iOS沙盒机制介绍 (转载) 1)概念:每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用放入文件 系统隔离,ios系统不允许访问 其他应用的应用沙盒,但在ios8中已经开放访 ...

  6. Linux 内核的文件 Cache 管理机制介绍

    Linux 内核的文件 Cache 管理机制介绍 http://www.ibm.com/developerworks/cn/linux/l-cache/ 1 前言 自从诞生以来,Linux 就被不断完 ...

  7. Mysql锁机制介绍

    Mysql锁机制介绍 一.概况MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制.比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking ...

  8. redis锁机制介绍与实例

    转自:https://m.jb51.net/article/154421.htm 今天小编就为大家分享一篇关于redis锁机制介绍与实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要 ...

  9. 线程安全的集合类、CopyOnWrite机制介绍(转)

    看过并发编程的书,这两种机制都有所了解,但不扎实其实.看到别人的博客描述的很精辟,于是转过来,感谢! 原文链接:https://blog.csdn.net/yen_csdn/article/detai ...

随机推荐

  1. “短路求值(Short-Circuit Evaluation)

        // 逻辑与和逻辑或操作符总是先计算其做操作数,只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数. function aa() { if (null) { console ...

  2. word20170104办签证 Visa application有用的词和句子

    有用的词:visa category: 签证类型tourist visa: 旅游签证visa interview: 签证面试multiple entries: 多次往返visa on arrival: ...

  3. opencv基础教程 之 图像基础和绘图

    1,教程:感谢小强 2,用argparse传参数来显示一张图片 #!/usr/bin/python #linux系统 #coding=utf-8 import cv2 import argparse ...

  4. 【译】索引进阶(八):SQL SERVER唯一索引

    [译注:此文为翻译,由于本人水平所限,疏漏在所难免,欢迎探讨指正] 原文链接:传送门. 在本章节我们检查唯一索引.唯一索引的特别之处在于它不仅提供了性能益处,而且提供了数据完整性益处.在SQL SER ...

  5. Linux下定时备份文件

    一. 编写脚本 编写一个脚本文件,使脚本可以执行备份命令. 例如,将文件目录 /home/backups/balalala 备份到/home目录下,并压缩. 1. 创建脚本 命令格式: touch 路 ...

  6. find your present (2) hdoj 2095

    /* author:谦智 find your present (2) hdoj 2095 法一:用暴力 法二:用map 法三: 符号是^. 异或是个位运算符号,具体是怎么操作的请百度,这里有个特性使得 ...

  7. Nginx:Linux下安装Nginx与配置

    准备目录 [root@sijizhen ~]# mkdir /usr/local/nginx [root@sijizhen ~]# cd /usr/local/nginx/ 下载 1.Nginx,在h ...

  8. Java中值传递和引用传递的区别

    在Java中参数的传递主要有两种:值传递和参数传递: 下面是对两种传递方式在内存上的分析: 一:值传递 解释:实参传递给形参的是值  形参和实参在内存上是两个独立的变量 对形参做任何修改不会影响实参 ...

  9. python面试题---收藏的笔记

    第一部分 Python基础篇(80题) 为什么学习Python? 通过什么途径学习的Python? Python和Java.PHP.C.C#.C++等其他语言的对比? 简述解释型和编译型编程语言? P ...

  10. YARN配置

    环境搭建 mapred-site.xml <configuration> <property> <name>mapreduce.framework.name< ...