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 ...
随机推荐
- CentOS命令行连接带密码的wifi
安装工具包 yum install -y wpa_supplicant 确定我们要连接的wifi的名称和密码,名称为ESSID.这里假设wifi名称为TPLINK,假设密码为password,下面请注 ...
- SpringDataJPA快速入门
访问我的博客 前言 之前在学习 SpringBoot 框架的时候,使用到了 SpringData JPA,但是当时只是简单的查询,没有用到稍微复杂的查询. JPA 的 JPQL 语法规则对于简单的查询 ...
- 面试:C++输入数据
最近在做笔试题,相比与leetcode,笔试题都是要自己写输入输出的,每次在这里都浪费了不少时间,这篇文章总结了一下在C++中怎么向数组中输入数据. 1. 先输入数组大小,然后输入数据数据,中间以空格 ...
- 第三章 使用Servlet处理HTTP响应
回顾上一章的知识: Java Servlet是运行在Web服务器或应用服务器上的Java程序 Servlet规范对Servlet功能进行了严格定义 Servlet API与容器进行通讯 Servlet ...
- vscode浏览器打开html vscode修改默认浏览器
vscode怎么浏览器打开html预览?这里大家可以通过安装open in browser插件解决. 1.vscode怎么浏览器预览 1.点击拓展 2.输入open in browser,选择第一个 ...
- angular $q promise详解
前言 通过本文,你大概能清楚angular promise是个啥,$q又是个啥,以及怎么用它.这里咱们先灌输下promise的思想. 下面写的全是废话,一些看着高逼格其实没什么大作用的概念,想知道$q ...
- switch-case最容易忽视的一点
switch语句是常用的一种java语法,但是往往最基本的,总是最容易被人们忽略. 首先,看下switch语句的基本结构: switch(表达式){ case 常量1: 语句1; break; cas ...
- 笨方法学python学习笔记
创建于:2016-02-29 更新于:03-02 python版本:2.7 %r 用来做 debug 比较好,因为它会显示变量的原始数据(raw data),而其它的符号则是用来向用户展示输出的: 每 ...
- 几句话的事儿,LogBack急速使用
1.说说logback 在java的世界里面好像从来只有log4j,因为它是在太有名气了.说实话,我也没觉得它那里不好,至少在我自己的项目里,它工作的好好的.但是主流有一种声音,在慢慢的放弃它,最始作 ...
- c# 跨域api
前端 ajax get请求 $.ajax({ url: "API地址", type: 'get', dataType: 'jsonp', async: true, processD ...