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. 只用一招,让你Maven依赖下载速度快如闪电

    一.背景 众所周知,Maven对于依赖的管理让我们程序员感觉爽的不要不要的,但是由于这货是国外出的,所以在我们从中央仓库下载依赖的时候,速度如蜗牛一般,让人不能忍,并且这也是大多数程序员都会遇到的问题 ...

  2. JavaScript -- Style

    -----055-Style.html----- <!DOCTYPE html> <html> <head> <meta http-equiv="c ...

  3. Silverlight中使用MVVM(2)-(提高)

    在第一篇文章中的示例中,我们已经简单的了解了应用MVVM模式的流程,我的本意是你已经了解了一点MVVM的概念,然后又没有一个较好的例子学习,可以跟着我一起学习MVVM模式,所以这个部分,都是没有理论知 ...

  4. 如何精准实现OCR文字识别?

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由云计算基础发表于云+社区专栏 前言 2018年3月27日腾讯云云+社区联合腾讯云智能图像团队共同在客户群举办了腾讯云OCR文字识别-- ...

  5. 通过HttpServletRequest获取服务器路径

    String scheme = request.getScheme(); String serverName = request.getServerName(); int port = request ...

  6. java虚拟机学习-Java常量池理解与总结(13-2)

    一.相关概念 什么是常量用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态变量.实例变量和局部变量,分别表示三种类型的常量. Class文件中的常量池在Clas ...

  7. vscode浏览器打开html vscode修改默认浏览器

    vscode怎么浏览器打开html预览?这里大家可以通过安装open in browser插件解决. 1.vscode怎么浏览器预览 1.点击拓展 2.输入open in browser,选择第一个 ...

  8. 用python写web一定要去破解的异步请求问题.经历web.py和tornado,完破!

    1.问题 上个学期,给学校写了一个数据服务,主要从oracle里面读取一些数据供查询使用,非常快速的用web.py搭建了起来.调试顺利,测试正常,上线!接下来就是挨骂了,我铁定知道会卡,但是没想到会那 ...

  9. [转]Magento 2 and 1 Million Products

    本文转自:https://www.goivvy.com/blog/magento-2-1-million-products Can Magento 2 handle 1 million product ...

  10. jQuery找到GridView控件ItemTemplate模版内的控件

    可以使用下面的方法,jQuery找到GridView控件ItemTemplate模版内的CheckBox: 使用jQuery的find()方法: