MVCC是实现高性能数据库的关键技术,主要为了读不影响写。几乎所有数据库系统都用这技术,比如Spanner,看这里。Percolator,看这里。当然还有mysql。本文说HBase的MVCC和0.98引入的新写线程模型。

HBase region server的存储模型类LSM,将随机写转换为顺序写,写操作直接写内存,然后写操作日志来持久化修改避免宕机丢数据。通常,为了提高性能,采用group commit技术,及多次修改一起写,一起写操作日志,充分利用磁盘的顺序IO。对于HBase来说,group commit在HRegion类doMiniBatchMutation(BatchOperationInProgress<?> batchOp)函数中,这里面实现了HBase的MVCC,本文主要分析该函数。

MVCC多版本控制协议,显然,数据(KeyValue)上需要被打上版本号,这样读的时候,就可以根据版本号过滤掉一些不可见的数据。HBase中有一个类MultiVersionConsistencyControl用来保存系统范围内的一些版本信息,比如写事务开始时会从MultiVersionConsistencyControl中拿memstoreWrite加1作为

本次写事务的版本号,随后这个事务写入的所有数据,以KeyValue的形式,都被打上了这个版本号。读事务开始时也会从MultiVersionConsistencyControl中拿

memstoreRead作为读事务的版本号,那么该读事务只能读取版本号小于等于这个版本号的数据(KeyValue)。组织KeyValue的核心数据结构是KeyValueSkipListSet,内部是JDK提供的ConcurrentSkipListMap,一个并发跳表实现。

HBase的事务实现简单来说,并发控制采用两阶段锁实现。这里省略一些细节,比如修正KeyValue的timestamp,数据的check等。

首先对于所有需要修改的行,一次性拿住所有行锁,然后调用mvcc对象的beginMemstoreInsert方法,获得一个WriteEntry对象,包含这次写事务的写版本号,通过mvcc.memstoreWrite加1获得,记作writeNumber,然后将WriteEntry放入队列writeQueue中,队列操作被锁保护。这个队列用来保存多个并发写事务的WriteEntry,方便后续推进mvcc.memstoreRead,memstoreRead作为读事务的事务版本号使用,这样当memstoreRead被推进,读事务可以读的数据就越来越新。然后,将batch里的数据都add到各个HStore的memstore中,每个数据KeyValue都被打上writeNumber,这没有问题,因为memstoreRead没有向前推进,故后续的读事务读不到这次数据。接着根据batch中的数据构建WALEdit,WALEdit相当于HLog中具体一条一条日志Entry的内容,Entry的头部是HLogKey结构,包含这条log entry对应的table name,region name,以及log entry的sequence number,region级别的,根据WALEdit和HLogKey组装成一个Entry后,然后将这个Entry 加到内存中的buffer pendingWrites中(还没开始写hdfs,只是写入内存中),然后为append的这条日志产生一个HLog范围内的id,记作txid(名字不是很恰当),txid不实际的存储在Entry中,只是用于标识这次写事务写入的日志,只有这些日志被实际的持久化到hdfs中后,请求才可以返回。

写入buffer后,即释放所有的行锁,两阶段锁的过程结束。最后,就是调用void syncer(long txid) 函数等待这次事务相应的日志被持久化到hdfs中(实际的写hdfs和sync是其他线程做的,牵扯到写线程模型,后续描述),一旦持久化完成,就标记一下WriteEntry,代表本次写事务对应的日志已持久化完成。然后就可以尝试去推进mvcc的memstoreRead。推进的过程实际上就是去writeQueue里从头到尾去看,找连续的已经完成的WriteEntry,最后一个WriteEntry的writeNumber即是最新的点,可以赋值给mvcc.memstoreRead,后续读事务一开始就去拿mvcc.memstoreRead,从而能都到最新的数据。这里需要一个队列的原因在于,写事务是并发的,有多个写线程同时都在执行写操作,先拿到memstoreWrite进队列的线程不一定先往pendingWrites中append,从而导致memstoreWrite更大的写事务的日志可能先被持久化到hdfs中。这里,writeQueue就是为了处理这种乱序的情况。最后,一个写事务什么时候可以返回给客户端?对于客户端来说,客户端希望后续可以看到自己之前成功commit的事务的数据,所以,只需要mvcc.memstoreRead 大于等于事务对应的WriteEntry的writeNumber即可。

现在说0.98引入的大幅提高吞吐量的写线程模型(HBASE-8755)。

和一个写事务有关的线程除了执行事务操作的工作线程外,还有如下几种:

1. 一个将内存中的pendingWrites写入HDFS(不sync)的线程,对应类AsyncWriter

2. 一个sync hdfs的线程,对应类AsyncSyncer

3. 一个sync完成后唤醒工作线程的线程,对应类AsyncNotifier

从工作线程开始,多个工作线程写内存中的pendingWrites,通过pendingWritesLock保护,写完后,得到txid,通过执行this.asyncWriter.setPendingTxid(txid) 去告诉AsyncWriter线程内存中有数据了,你可以往hdfs中写了,AsyncWriter加锁pendingWritesLock,将pendingWrites拿出来,解锁,然后将pendingWrites写入hdfs,接着找一个空闲的AsyncSyncer,通asyncSyncers[i].setWrittenTxid(this.lastWrittenTxid)

告诉它有新的数据需要sync了,AsyncSyncer调用AsyncWriter的sync操作, sync完成后,将最后sync的txid记录在变量AsyncSyncer中,然后调用asyncNotifier.setFlushedTxid(this.lastSyncedTxid) 通知AsyncNotifier 又sync完了一批,可以去唤醒工作线程,让他们自己看看是否自己当前执行事务的日志已经持久化。AsyncNotifier和工作线程通过syncedTillHere这个AtomicLong进行同步,AsyncNotifier会将最后一个sync成功的txid记录在syncedTillHere中,

工作线程会等在syncedTillHere上,每次被叫醒后,看看自己的txid是否小于等于syncedTillHere,条件满足则工作线程继续往下走,做推进mvcc点相关的工作。

HBase的写事务,MVCC及新的写线程模型的更多相关文章

  1. HBase的Write Ahead Log (WAL) —— 整体架构、线程模型

    解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回放机制.每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中. 如果出 ...

  2. HBase的Write Ahead Log (WAL) —— 整体架构、线程模型【转】

    转自:http://www.cnblogs.com/ohuang/p/5807543.html 解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回 ...

  3. HBase并行写机制(mvcc)

    HBase在保证高性能的同时,为用户提供了便于理解的一致性数据模型MVCC (Multiversion Concurrency Control),即多版本并发控制技术,把数据库的行锁与行的多个版本结合 ...

  4. HBase之七:事务和并发控制机制原理

    作为一款优秀的非内存数据库,HBase和传统数据库一样提供了事务的概念,只是HBase的事务是行级事务,可以保证行级数据的原子性.一致性.隔离性以及持久性,即通常所说的ACID特性.为了实现事务特性, ...

  5. 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)

    前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...

  6. 透彻理解Spring事务设计思想之手写实现

    前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...

  7. Ream--(objc)写事务精简方案

    Ream--(objc)写事务精简方案 地址: REALM-- Realm官方提供的的写事务有两种方式: A[realm beginWriteTransaction]; // ... [realm c ...

  8. Hbase WAL线程模型源码分析

    版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...

  9. Redis 新特性:多线程模型解读

    Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注. 主要特性如下: 多线程处理网络 IO: 客户端缓存: 细粒度权限控制(ACL): RESP3  ...

随机推荐

  1. vuex详细介绍和使用方法

    1.什么是vuex? 官方的解释: Vuex是一个专为Vue.js应用程序开发的状态管理模式 当项目比较庞大的时候,每个组件的状态比较多,为了方便管理,需要把组件中的状态抽取出来,放入Vuex中进行统 ...

  2. org.hibernate.NonUniqueObjectException:a different object with the same identifier value was alread

    转自: http://blog.csdn.net/zzzz3621/article/details/9776539 看异常提示意思已经很明显了,是说主键不唯一,在事务的最后执行SQL时,session ...

  3. (转)mybatis-plus入门

    目前正在维护的公司的一个项目是一个ssm架构的java项目,dao层的接口有大量数据库查询的方法,一个条件变化就要对应一个方法,再加上一些通用的curd方法,对应一张表的dao层方法有时候多达近20个 ...

  4. C#输出26个大写字母

    C#输出26个大写字母,较快的方法:

  5. vs2013 在win7下,使用c++创建项目各种报错问题解决方案

    错误1:提示缺少mfc100ud.dll 错误2:win7 vs2013 mfc程序找不到sdkddkver.h 错误3:error LNK1158: 无法运行“rc.exe” 等等; 在网上搜了好久 ...

  6. Java虚拟机--虚拟机字节码执行引擎

    Java虚拟机--虚拟机字节码执行引擎 所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果. 运行时栈帧结构 用于支持虚拟机进行方法调用和方 ...

  7. Tomcat9.0环境搭建与源码编译

    使用IntelliJ IDEA  搭建Tomcat9.0项目   准备条件:   下载源码 这里我们下载的Tomcat的源码版本是9.0.12. 下载地址: https://tomcat.apache ...

  8. Java-函数式编程(二)Lambda表达式

    本文首发:Java-函数式编程(二)Lambda表达式 “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lamb ...

  9. 转载 - java中接口的向上转型。和多态性

    发现一篇对接口总结很精简的文章 1.在java中接口就是一个完全抽象的类,跟抽象类一样不能产生对象,但是可以作为对象的引用,可以由其实现类向上转型,它就跟超类一样, 向上转型了,可以很好的利用接口,可 ...

  10. POJ3090(SummerTrainingDay04-M 欧拉函数)

    Visible Lattice Points Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 7450   Accepted: ...