HBase的写事务,MVCC及新的写线程模型
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及新的写线程模型的更多相关文章
- HBase的Write Ahead Log (WAL) —— 整体架构、线程模型
解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回放机制.每一个业务数据的写入操作(PUT / DELETE)执行前,都会记账在WAL中. 如果出 ...
- HBase的Write Ahead Log (WAL) —— 整体架构、线程模型【转】
转自:http://www.cnblogs.com/ohuang/p/5807543.html 解决的问题 HBase的Write Ahead Log (WAL)提供了一种高并发.持久化的日志保存与回 ...
- HBase并行写机制(mvcc)
HBase在保证高性能的同时,为用户提供了便于理解的一致性数据模型MVCC (Multiversion Concurrency Control),即多版本并发控制技术,把数据库的行锁与行的多个版本结合 ...
- HBase之七:事务和并发控制机制原理
作为一款优秀的非内存数据库,HBase和传统数据库一样提供了事务的概念,只是HBase的事务是行级事务,可以保证行级数据的原子性.一致性.隔离性以及持久性,即通常所说的ACID特性.为了实现事务特性, ...
- 透彻理解Spring事务设计思想之手写实现(山东数漫江湖)
前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...
- 透彻理解Spring事务设计思想之手写实现
前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原子性),Consistency(一致性),Isolation(隔离性),D ...
- Ream--(objc)写事务精简方案
Ream--(objc)写事务精简方案 地址: REALM-- Realm官方提供的的写事务有两种方式: A[realm beginWriteTransaction]; // ... [realm c ...
- Hbase WAL线程模型源码分析
版权声明:本文由熊训德原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/257 来源:腾云阁 https://www.qclo ...
- Redis 新特性:多线程模型解读
Redis 官方在 2020 年 5 月正式推出 6.0 版本,提供很多振奋人心的新特性,所以备受关注. 主要特性如下: 多线程处理网络 IO: 客户端缓存: 细粒度权限控制(ACL): RESP3 ...
随机推荐
- 解决docker镜像无法下载的问题
从daocloud.io中找到了获取镜像的方式,在镜像仓库中可以找到镜像的地址,其他镜像地址可以以此类推: # docker pull daocloud.io/library/centos:lates ...
- 基于Hadoop2.6.5(HA)的Hive1.2.1的MySQL方式配置
1.Hive配置MySQL Hive只是一个工具,无需配置多台机器,我在CentOS7One机器上配置Hive /usr/local/hive/apache-hive-1.2.1-bin/conf c ...
- Java设计模式学习记录-适配器模式
前言 之前已经将五个创建型设计模式介绍完了,从这一篇开始介绍结构型设计模式,适配器模式就是结构型模式的一种,适配器要实现的效果是把“源”过渡到“目标”. 适配器模式 在开发过程中,使用一个已经存在的类 ...
- Linux 卸载 openjdk
1 卸载 openjdk sudo apt-get purge openjdk*
- UVA 11582 Colossal Fibonacci Numbers!(循环节打表+幂取模)
题目链接:https://cn.vjudge.net/problem/UVA-11582 /* 问题 输入a,b,n(0<a,b<2^64(a and bwill not both be ...
- HTTP 无法注册URL 进程不具有命名空间的访问权限
写WCF时在 host.Open(); 报错:HTTP 无法注册 URL http://+:9999/CalculatorService/.进程不具有此命名空间的访问权限(有关详细信息,请参见 htt ...
- 并发编程——ConcurrentHashMap#transfer() 扩容逐行分析
前言 ConcurrentHashMap 是并发中的重中之重,也是最常用的数据结果,之前的文章中,我们介绍了 putVal 方法.并发编程之 ConcurrentHashMap(JDK 1.8) pu ...
- webpack3新特性简介
6月20号webpack推出了3.0版本,官方也发布了公告.根据公告介绍,webpack团队将未来版本的改动聚焦在社区提出的功能需求,同时将保持一个快速.稳定的发布节奏.本文主要依据公告内容,简单介绍 ...
- [转]DevOps实战:百度持续交付体系与最佳实践大解密!
本文转自:http://dbaplus.cn/news-21-471-1.html “互联网+”时代,软件产品要想满足快速增长的用户需求,高效.快速的迭代转型必不可少,面对时刻发生改变的互联网及业务模 ...
- C#取整函数Math.Round、Math.Ceiling和Math.Floor 【非原创,用来收藏,分享】
1.Math.Round:四舍六入五取偶 引用内容 Math.Round(0.0) //0Math.Round(0.1) //0Math.Round(0.2) //0Math.Round(0.3) / ...