关键词: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. [转]在static代码块或static变量的初始化过程中使用ServiceManager提供的api的陷阱

    一. 案例 1.源码: /** @hide */ private TelephonyManager(int slotId) { mContext = null; mSlotId = slotId; i ...

  2. 关于微信emoji 表情数据库存不了,或者显示为???的问题

    必须我utf8mb4,数据库就可以存 2. 数据库连接也需要是utf8mb4

  3. 基于注解的SpringMVC自定义DispatcherServlet配置

    通过重载AbstractAnnotationConfigDispatcherServletInitializer实现类的customizeRegistration()方法来自定义DispatcherS ...

  4. 通过hook实现禁止shift+delete快捷键

    实现全局hook必须要将hook代码封装在dll里,所以此程序有两个文件:noShiftDeleteHook.dll和noShiftDelete.exe noShiftDeleteHook.dll / ...

  5. html 文字少则居中多则居左

    <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="index ...

  6. VMware workstation 上克隆CentOS 6.x 系统后网卡无法启动的问题

    在日常学习中,我们往往没有足够的物理机资源来搭建多节点的实验环境,一个比较好的解决方案就是利用虚拟机来模拟物理机完成实验. 这样一来,多节点操作系统的部署就可以利用VMware 自带的系统“克隆”功能 ...

  7. java 静态代理模式

    package proxy.staticproxy; public interface IStar { public void sing(); } package proxy.staticproxy; ...

  8. python的学习之路(二)

    1.字符串内置功能练习#!/usr/bin/env python# *_*coding:utf-8 *_*# Author: harsonname = 'harson'name =str('harso ...

  9. OPPO F9 Pro在哪里打开usb调试模式的完美方法

    经常我们使用pc通过数据线连接到安卓手机的时候,如果手机没有开启USB调试模式,pc则没能够成功读到我们的手机,此情况我们需要找处理方法将手机的USB调试模式开启,今天我们介绍OPPO F9 Pro如 ...

  10. 【JAVA】HashMap的原理及多线程下死循环的原因

    再次翻到以前工作中遇到的一个问题,HashMap在多线程下会出现死循环的问题,以前只是知道会死循环,导致CPU100%把机器拖跨,今天来彻底看看 首先来看下,HashMap的原理:HashMap是一个 ...