HBase MVCC 机制介绍
关键词: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 机制介绍的更多相关文章
- HBase MVCC 代码阅读(一)
MultiVersionConcurrencyControl.java,版本 0.94.1 MultiVersionConsistencyControl 管理 memstore 中的读写一致性.该类实 ...
- MySQL多版本并发控制——MVCC机制分析
MVCC,即多版本并发控制(Multi-Version Concurrency Control)指的是,通过版本链维护一个数据的多个版本,使得读写操作没有冲突,可保证不同事务读写.写读操作并发执行,提 ...
- MySQL 学习笔记(二)MVCC 机制
之前在讲 MySQL 事务隔离性提到过,对于写操作给读操作的影响这种情形下发生的脏读.不可重复读.虚读问题.是通过MVCC 机制来进行解决的,那么MVCC到底是如何实现的,其内部原理是怎样的呢?我们要 ...
- iOS 阶段学习第25天笔记(iOS沙盒机制介绍)
iOS学习(OC语言)知识点整理 一.iOS沙盒机制介绍 1)概念: 每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用放入文件 系统隔离,ios系统不允许访问 其他应用的应用沙盒 ...
- iOS沙盒机制介绍,Block 的介绍
一.iOS沙盒机制介绍 (转载) 1)概念:每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用放入文件 系统隔离,ios系统不允许访问 其他应用的应用沙盒,但在ios8中已经开放访 ...
- Linux 内核的文件 Cache 管理机制介绍
Linux 内核的文件 Cache 管理机制介绍 http://www.ibm.com/developerworks/cn/linux/l-cache/ 1 前言 自从诞生以来,Linux 就被不断完 ...
- Mysql锁机制介绍
Mysql锁机制介绍 一.概况MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制.比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking ...
- redis锁机制介绍与实例
转自:https://m.jb51.net/article/154421.htm 今天小编就为大家分享一篇关于redis锁机制介绍与实例,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要 ...
- 线程安全的集合类、CopyOnWrite机制介绍(转)
看过并发编程的书,这两种机制都有所了解,但不扎实其实.看到别人的博客描述的很精辟,于是转过来,感谢! 原文链接:https://blog.csdn.net/yen_csdn/article/detai ...
随机推荐
- hadoop启动 datanode的live node为0
hadoop启动 datanode的live node为0 浏览器访问主节点50070端口,发现 Data Node 的 Live Node 为 0 查看子节点的日志 看到 可能是无法访问到主节点的9 ...
- dubbo基本信息
1.Dubbo是什么? Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,现已成为 Apache 基金会孵化项目. 面试官问你如果这个都不清楚,那下面的就没必要问了. 官网: ...
- 关于VC预定义常量_WIN32,WIN32,_WIN64等预定义宏的介绍(整理、转载)
参考帖子: (1)MSDN上专门讲预定义宏:https://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx (2)VS中属性页的配置介绍 ...
- dataguard丢失归档日志处理
检查alert日志发现报错如下 Wed Mar 27 15:40:30 2019Managed Standby Recovery not using Real Time ApplyParallel M ...
- 关于npm镜像,发布,内网搭建等
npm config set registry http://registry.npmjs.org npm config set registry https://registry.npm.taoba ...
- STM32 USB 鼠标+键盘 串口控制
*MOS0101000000# 鼠标左键按下 *MOS0102000000# 鼠标右键按下 *MOS0103000000# 鼠标中键按下 *MOS0100000000# 鼠标抬起 *MOS01000a ...
- 2018-2019-2 20165314『网络对抗技术』Exp5:MSF基础应用
一.原理与实践说明 1.实践内容 本实践目标是掌握metasploit的基本应用方式,重点常用的三种攻击方式的思路.具体需要完成: 一个主动攻击实践,如ms08-067; (1分) 一个针对浏览器的攻 ...
- jdk7和8中关于HashMap和concurrentHashMap的扩容过程总结,以及HashMap死循环
题外话:为什么要hashcode进行spread? 充分使用key.hashCode()的高16位信息,保证hash分布更分散, 扩容操作是新建2倍于原表大小的新表,并将原表结点拷贝一份放在新表中,对 ...
- 关于解决微信支付sdk中NoClassDefFoundError: Failed resolution of: org.apache.http.conn.ssl.DefaultHostnameVerifier;
导入依赖<dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>ht ...
- web页面和小程序页面实现瀑布流效果
小程序实现瀑布流效果,和web页面差不多,都要经过以下步骤: 1).加载图片,获取图片的宽高度: 2).根据页面需要显示几列计算每列的宽度: 3).根据图片真实宽度和每列的宽度比,计算出图片需要显示的 ...