关键词: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. sql注入中关于--+的一点探索

    在sql-labs游戏中,经常使用--+放在最后注释多余部分,而mysql中的注释符为#和-- 却不能直接使用,以前没学过mysql,一直不理解,也不知道+号的作用,今天有时间特地探索了一下,算是搞明 ...

  2. 没有显示器如何SSH连接上树莓派

    1.在用读卡器烧录系统后先用Linux虚拟机连接上读卡器,修改 sudo gedit /etc/wpa_supplicant/wpa_supplicant.conf 加入 network={ ssid ...

  3. pythonのdjango Form简单应用。

    Form表单有两种应用场景: 1.生成HTML标签. 2.验证输入内容. 如果我们在django程序中使用form时,需要在views中导入form模块 from django import form ...

  4. 学习笔记——单片机简介 & 点亮LED & 流水灯 & 电路基础【更新Ing】

    视频地址:https://www.bilibili.com/video/av10765766 超详细!!!!!! 单片机内部三大资源 [资源:单片机可提供使用的东西] FLASH 可以重复擦写 断电后 ...

  5. Access denied for user ‘root’@‘localhost’(using password: YES)的解决方法

    https://www.jb51.net/article/133836.htm 搭建服务器之后只能看见test与infomation_schema两个库 https://www.cnblogs.com ...

  6. jmeter生成html格式接口自动化测试报告

    jmeter生成html格式接口自动化测试报告 jmeter自带执行结果查看的插件,但是需要在jmeter工具中才能查看,如果要向领导提交测试结果,不够方便直观. 笔者刚做了这方面的尝试,总结出来分享 ...

  7. TopN案例

    准备三份数据 t1 2067 t2 2055 t3 2055 t4 1200 t5 2367 t6 255 t7 2555 t8 12100 t9 20647 t10 245 t11 205 t12 ...

  8. SSM框架中写sql在xml文件中

    第一种(用Mapper.xml映射文件中定义了操作数据库sql) 注意点: 1.#{}与${} #{}表示一个占位符,使用占位符可以防止sql注入, ${}通过${}可以将parameterType传 ...

  9. MYSQL 单表一对多查询,将多条记录合并成一条记录

    一.描述: 在MySQL 5.6环境下,应工作需求:将一个表中多条某个相同字段的其他字段合并(不太会表达,有点绕,直接上图) 想要达到的效果: 实现SQL语句: SELECT a.books, GRO ...

  10. VMware与Hyper-V的冲突解决 VMware Workstation 与 Device/Credential Guard 不兼容 解决方案

    win10专业版官方解决方案https://kb.vmware.com/s/article/2146361 win10家庭版解决方案win10家庭版本身是不支持Hyper-V服务的,但是如果是“win ...