HBase作为分布式NoSQL数据库系统,不单支持宽列表。而且对于随机读写来说也具有较高的性能。在高性能的随机读写事务的同一时候。HBase也能保持事务的一致性。

眼下HBase仅仅支持行级别的事务一致性。本文主要探讨一下HBase的写请求流程。主要基于0.98.8版本号的实现。

client写请求

   HBase提供的Java client API是以HTable为主要接口,相应当中的HBase表。

写请求API主要为HTable.put(write和update)、HTable.delete等。

以HTable.put为样例,首先来看看客户端是怎么把请求发送到HRegionServer的。

每一个put请求表示一个KeyValue数据,考虑到client有大量的数据须要写入到HBase表,HTable.put默认是会把每一个put请求都放到本地缓存中去,当本地缓存大小超过阀值(默觉得2MB)的时候,就要请求刷新,即把这些put请求发送到指定的HRegionServer中去,这里是利用线程池并发发送多个put请求到不同的HRegionServer。

但假设多个请求都是同一个HRegionServer,甚至是同一个HRegion,则可能造成对服务端造成压力,为了避免发生这样的情况,clientAPI会对写请求做了并发数限制,主要是针对put请求须要发送到的HRegionServer和HRegion来进行限制。详细实如今AsyncProcess中。

主要參数设定为:

  • hbase.client.max.total.tasks              客户端最大并发写请求数。默觉得100
  • hbase.client.max.perserver.tasks      客户端每一个HRegionServer的最大并发写请求数。默觉得2
  • hbase.client.max.perregion.tasks      客户端每一个HRegion最大并发写请求数。默觉得1

为了提高I/O效率。AsyncProcess会合并同一个HRegion相应的put请求,然后再一次把这些同样HRegion的put请求发送到指定HRegionServer上去。另外AsyncProcess也提供了各种同步的方法,如waitUntilDone等,方便某些场景下必须对请求进行同步处理。每一个put和读请求一样。都是要通过訪问hbase:meta表来查找指定的HRegionServer和HRegion,这个流程和读请求一致,能够參考文章的描写叙述。

服务端写请求

    当client把写请求发送到服务端时。服务端就要開始运行写请求操作。HRegionServer把写请求转发到指定的HRegion运行,HRegion每次操作都是以批量写请求为单位进行处理的。主要流程实如今HRegion.doMiniBatchMutation,大致例如以下:

  1. 获取写请求里指定行的行锁。

    因为这些批量写请求之间是不保证一致性(仅仅保证行一致性),因此每次仅仅会尝试堵塞获取至少一个写请求的行锁。其他已被获取的行锁则跳过这次更新。等待下次迭代的继续尝试获取

  2. 更新已经获得行锁的写请求的时间戳为当前时间
  3. 获取HRegion的updatesLock的读锁。
  4. 获取MVCC(Multi-Version Concurrency Control)的最新写序号,和写请求KeyValue数据一起写入到MemStore。
  5. 构造WAL(Write-Ahead Logging) edit对象
  6. 把WAL edit对象异步加入到HLog中。获取txid号
  7. 释放第3步中的updatesLock的读锁以及第1步中获得的行锁
  8. 依照第6步中txid号同步HLog
  9. 提交事务。把MVCC的读序号前移到第4步中获取到的写序号
  10. 假设以上步骤出现失败,则回滚已经写入MemStore的数据
  11. 假设MemStore缓存的大小超过阀值,则请求当前HRegion的MemStore刷新操作。

经过以上步骤后,写请求就属于被提交的事务,后面的读请求就能读取到写请求的数据。这些步骤里面都包括了HBase的各种特性,主要是为了保证可观的写请求的性能的同一时候。也确保行级别的事务ACID特性。接下来就详细分析一下一些主要步骤的详细情况。

HRegion的updatesLock

步骤3中获取HRegion的updatesLock,是为了防止MemStore在flush过程中和写请求事务发生线程冲突。

首先要知道MemStore在写请求的作用。

HBase为了提高读性能。因此保证存储在HDFS上的数据必须是有序的,这样就能使用各种特性,如二分查找,提升读性能。但因为HDFS不支持改动,因此必须採用一种措施把随机写变为顺序写。MemStore就是为了解决问题。

随机写的数据写如MemStore中就行在内存中进行排序。当MemStore大小超过阀值就须要flush到HDFS上,以HFile格式进行存储。显然这个HFile的数据就是有序的,这样就把随机写变为顺序写。另外。MemStore也是HBase的LSM树(Log-Structured
Merge Tree)的实现部分之中的一个。

在MemStore进行flush的时候。为了避免对读请求的影响,MemStore会对当前内存数据kvset创建snapshot。并清空kvset的内容,读请求在查询KeyValue的时候也会同一时候查询snapshot。这样就不会受到太大影响。可是要注意。写请求是把数据写入到kvset里面。因此必须加锁避免线程訪问发生冲突。因为可能有多个写请求同一时候存在,因此写请求获取的是updatesLock的readLock。而snapshot同一时间仅仅有一个,因此获取的是updatesLock的writeLock。

获取MVCC写序号

MVCC是HBase为了保证行级别的事务一致性的同一时候,提升读请求的一种并发事务控制的机制。MVCC的机制不难理解,能够參考这里

MVCC的最大优势在于,读请求和写请求之间不会互相堵塞冲突,因此读请求一般不须要加锁(仅仅有两个写同一行数据的写请求须要加锁),仅仅有当写请求被提交了后,读请求才干看到写请求的数据,这样就能够避免发生“脏读”。保证了事务一致性。详细MVCC实现能够參考HBase的一位PMC Member的这篇文章

WAL(Write-Ahead Logging) 与HLog

    WAL是HBase为了避免遇到节点故障无法服务的情况下。能让其他节点进行数据恢复的机制。

HBase进行写请求操作的时候。默认都会把KeyValue数据写入封装成WALEdit对象,然后序列化到HLog中。在0.98.8版本号里採用ProtoBuf格式进行序列化WAL。HLog是记录HBase改动的日志文件,和数据文件HFile一样,也是存储于HDFS上。因此保证了HLog文件的可靠性。这样假设机器发生宕机。存储在MemStore的KeyValue数据就会丢失。HBase就能够利用HLog里面记录的改动日志进行数据恢复。

每一个HRegionServer仅仅有一个HLog对象。因此当前HRegionServer上全部的HRegion的改动都会记录到同一个日志文件里,在须要数据恢复的时候再慢慢依照HRegion切割HLog里的改动日志(Log Splitting)。

整个写请求里,WALEdit对象序列化写入到HLog是唯一会发生I/O的步骤。这个会大大影响写请求的性能。当然,假设业务场景对数据稳定性要求不高,关键是写入请求,那么能够调用Put.setDurability(Durability.SKIP_WAL)。这样就能够跳过这个步骤。

HBase为了减轻写入HLog产生I/O的影响,採用了较为粒度较细的多线程并发模式(具体可參考HBASE-8755)。HLog的实现为FSHLog,主要过程涉及三个对象:AsyncWriter、AsyncSyncer和AsyncNotifier。整个写入过程涉及步骤5-8。

  1. HRegion调用FSHLog.appendNoSync,把改动记录加入到本地buffer中,通知AsyncWriter有记录插入,然后返回一个long型递增的txid作为这条改动记录。注意到这是一个异步调用。

  2. HRegion之后会立即释放updatesLock的读锁以及获得的行锁,然后再调用FSHLog.sync(txid),来等待之前的改动记录写入到HLog中。
  3. AsyncWriter从本地buffer取出改动记录,然后将记录经过压缩以及ProtoBuf序列化写入到FSDataOutputStream的缓存中,然后再通知AsyncSyncer。因为AsyncSyncer的工作量较大,因此总共同拥有5条线程,AsyncWriter会选择当中一条进行唤醒。

  4. AsyncSyncer推断是否有其他AsyncSyncer线程已经完毕了同步任务,假设是则继续等待AsyncWriter的同步请求。否则的话就把FSDataOutputStream的缓存写入到HDFS中去,然后唤醒AsyncNotifier
  5. AsyncNotifier的任务较为简单,仅仅是把全部正在等待同步的写请求线程唤醒。只是其实该过程相同较为耗时。因此另外分出AsyncNotifier线程。而不是在AsyncSyncer完毕通知任务。
  6. HRegion被唤醒。发现自己的txid已经得到同步,也就是改动记录写入到HLog中。于是接着其他操作。

在以上的写入过程中。第2步里HRegion先把记录写入HLog的buffer,然后再释放之前获得的锁后才同步等待写入完毕,这样可以有效减少锁持有的时间,提高其他写请求的并发。

另外,AsyncWriter、AsyncSyncer和AsyncNotifier组成的新的写模型主要负担起HDFS写操作的任务。对照起旧的写模型(须要每一个写请求的线程来负责写HDFS。大量的线程导致严重的锁竞争),最主要是大大减少了线程同步过程中的锁竞争,有效地提高了线程的吞吐量。这个写过程对于大批量写请求来说。可以提高吞吐量,但对于写请求并发量较小,线程竞争较低的环境下,因为每一个写请求必须等待Async*线程之间的同步,添加了线程上下文切换的开销,会导致性能略微下降(在0.99版本号里採用了LMAX
Disruptor同步模型。并把FSHLog进行了重构。HBASE-10156)。

MVCC读序号前移

    完毕HLog的写之后,整个写请求事务就已经完毕流程。因此就须要提交事务,让其他读请求能够看到这个写请求的数据。前面已经稍微介绍过MVCC的作用,这里关注一下MVCC是怎样处理读序号前移。

MVCC在内部维持一个long型写序号memstoreWrite,一个long型读序号memstoreRead,另一个队列writeQueue。

当HRegion调用beginMemStoreInsert要求分配一个写序号的时候,就会把写序号自增1。并返回,并同一时候把一个写请求加入到writeQueue尾部。代码例如以下:

public WriteEntry beginMemstoreInsert() {
synchronized (writeQueue) {
long nextWriteNumber = ++memstoreWrite;
WriteEntry e = new WriteEntry(nextWriteNumber);
writeQueue.add(e);
return e;
}
}

HRegion把这个写序号和每一个新插入的KeyValue数据进行关联。当写请求完毕的时候,HRegion调用completeMemstoreInsert请求读序号前移。MVCC首先把写请求记录为完毕,然后查看writeQueue队列,从队列头部開始取出全部已经完毕的写请求。最后一个完毕的写请求的序号则会赋值给memstoreRead,表示这是当前最大可读的读序号。假设HRegion的写请求的序号比读序号要小,则完毕了事务提交。否则HRegion会一直循环等待提交完毕。

相关代码例如以下:

public void completeMemstoreInsert(WriteEntry e) {
advanceMemstore(e);
waitForRead(e);
} boolean advanceMemstore(WriteEntry e) {
synchronized (writeQueue) {
e.markCompleted();
long nextReadValue = -1;
while (!writeQueue.isEmpty()) {
ranOnce=true;
WriteEntry queueFirst = writeQueue.getFirst();
//...
if (queueFirst.isCompleted()) {
nextReadValue = queueFirst.getWriteNumber();
writeQueue.removeFirst();
} else {
break;
}
} if (nextReadValue > 0) {
synchronized (readWaiters) {
memstoreRead = nextReadValue;
readWaiters.notifyAll();
}
}
if (memstoreRead >= e.getWriteNumber()) {
return true;
}
return false;
}
} public void waitForRead(WriteEntry e) {
boolean interrupted = false;
synchronized (readWaiters) {
while (memstoreRead < e.getWriteNumber()) {
try {
readWaiters.wait(0);
} catch (InterruptedException ie) {
//...
}
}
}
}

由此可见,MVCC保证了事务提交的串行顺序性,假设有某个写请求提交成功,则不论什么写序号小于这个写序号的写请求必定提交成功。因此在读请求的时候,仅仅要获取MVCC的读请求序号则能够读取不论什么最新提交成功写请求的写数据。另外,MVCC仅仅限制在事务提交的这个过程的串行,在实际的写请求过程中,其他步骤都是同意并发的。因此不会对性能造成太大的影响。

至此,HBase的一个写请求的事务提交过程就完毕。在整个写过程里。都採用了大量的方法去避免锁竞争、缩短获取锁的时间以及保证事务一致性等措施。因为MemStore在内存的缓存上始终有限制大小,因此当MemStore超过阀值的时候,HBase就要刷新数据到HDFS上。形成新的HFile。

接下来看看这个过程。

MemStore的flush

当大量的写请求数据加入到MemStore上。MemStore超过阀值,HRegion就会请求把MemStore的数据flush到HDFS上。另外要注意到的是,这里flush的单位是单个HRegion。也就是说假设有多个HStore,仅仅要有一个MemStore超过阀值。这个HRegion所属的全部HStore都要运行flush操作。

  • HRegion首先要获取updatesLock的写锁,这样就防止有新的写请求到来
  • 请求获取MVCC的写序号
  • 请求MemStore生成snapshot
  • 释放updatesLock的写锁
  • 提交之前获取的MVCC写序号,等待之前的事务完毕,防止回滚事务写入HFile
  • 把snapshot的KeyValue数据写入到HFile里

主要集中来看看把snapshot的KeyValue数据写入HFile部分。先来看看HFile的格式:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

之前在读请求文章里已经介绍个HFile的这个格式。HFile要保证每一个HBlock大小约为64KB,採用DataBlock多级索引和BloomFilter一级索引的方法来构成HFile结构。

整个写过程比較简单,在循环里便利获取MemStore的snapshot的KeyValue数据,然后不断写DataBlock里,假设当前DataBlock的总大小超过64KB,则DataBlock就停止加入数据(设置了压缩会进行压缩处理),同一时候计算DataBlock的索引,并加入到内存中。另外假设开启了BloomFilter属性也要写入相应的BloomBlock,这个过程中会注意保存未压缩大小等FileInfo数据。

当全部的snapshot数据都写入完DataBlock中后,就要開始写入DataBlock的多级索引了。HBase会依据之前保存的索引计算多级索引的级数,假设索引数量不多,则有可能仅仅有RootIndexBlock一个级别。同一时候也会依据RookIndexBlock获得MidKey的数据。最后就依照顺序写入FileInfo以及BloomFilter的索引,还有Trailer。

总结

HBase採用了MemStore把随机写变为顺序写,这样有助于提高读请求的效率。另外也为了避免数据丢失使用HLog来记录改动日志。在整个写过程中。使用了多种手段减轻了锁竞争,提高了线程吞吐量,也注意缩短锁获取的时间,尽可能地提高并发。通过利用MVCC也避免了读写请求之间的影响。

HBase写请求分析的更多相关文章

  1. HBase写被block的分析

    一个线上集群出现莫名奇妙不能写入数据的bug,log中不断打印如下信息: 引用 2011-11-09 07:35:45,911 INFO org.apache.hadoop.hbase.regions ...

  2. HBase工程师线上工作经验总结----HBase常见问题及分析

    阅读本文可以带着下面问题:1.HBase遇到问题,可以从几方面解决问题?2.HBase个别请求为什么很慢?你认为是什么原因?3.客户端读写请求为什么大量出错?该从哪方面来分析?4.大量服务端excep ...

  3. (转)HBase工程师线上工作经验总结----HBase常见问题及分析

    阅读本文可以带着下面问题:1.HBase遇到问题,可以从几方面解决问题?2.HBase个别请求为什么很慢?你认为是什么原因?3.客户端读写请求为什么大量出错?该从哪方面来分析?4.大量服务端excep ...

  4. 分布式存储系统Kudu与HBase的简要分析与对比

    本文来自网易云社区 作者:闽涛 背景 Cloudera在2016年发布了新型的分布式存储系统——kudu,kudu目前也是apache下面的开源项目.Hadoop生态圈中的技术繁多,HDFS作为底层数 ...

  5. Hbase源码分析:Hbase UI中Requests Per Second的具体含义

    Hbase源码分析:Hbase UI中Requests Per Second的具体含义 让运维加监控,被问到Requests Per Second(见下图)的具体含义是什么?我一时竟回答不上来,虽然大 ...

  6. Hbase写数据,存数据,读数据的详细过程

    Client写入 -> 存入MemStore,一直到MemStore满 -> Flush成一个StoreFile,直至增长到一定阈值 -> 出发Compact合并操作 -> 多 ...

  7. HBase写数据

    1 多HTable并发写 创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子: static final Configuration conf = HBaseConfiguration ...

  8. HBase的compact分析

    HBase是基于LSM树存储模型的分布式NoSQL数据库.LSM树对比普遍的B+树来说,能够获得较高随机写性能的同时,也能保持可靠的随机读性能(可参考这里).在进行读请求的时候,LSM树要把多个子树( ...

  9. HBase Scan流程分析

    HBase Scan流程分析 HBase的读流程目前看来比较复杂,主要由于: HBase的表数据分为多个层次,HRegion->HStore->[HFile,HFile,...,MemSt ...

随机推荐

  1. animation总结

    1. animation结束后停在最后一帧 animation-fill-mode : forwards | both; /* 或者 */ animation: anim1 1s linear for ...

  2. java中文乱码问题解决

    1 处理乱码方式: 1 连接数据库的时候 jdbc.properties:jdbc:mysql://localhost:3306/myproject?useUnicode=true&chara ...

  3. P2134 百日旅行 (斜率优化,DP)

    题目链接 Solution 斜率优化\(DP\). 今天下午才打的第一道题 QwQ... \(90\) 分很简单,一个简单的递推. 令 \(f[i]\) 为最后一天旅游的花费, \(g[i]\) 为最 ...

  4. select * from 为什么效率低?

    sql优化有很重要的一项叫做列裁剪(column pruning).如果不考虑索引,sql的执行算法大概分为sort-base和hash-base,不论是哪种,多出来的列都会带来很多无用的计算. “* ...

  5. UVA 116 Unidirectional TSP(DP最短路字典序)

    Description    Unidirectional TSP  Background Problems that require minimum paths through some domai ...

  6. java内部类的四大作用

    一.定义 放在一个类的内部的类我们就叫内部类. 二. 作用 1.内部类可以很好的实现隐藏 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 2.内部类拥有外围类的所 ...

  7. 使用C语言和i2c-dev驱动

    原文地址:blog.csdn.NET/wyt2013/article/details/20740659 感谢作者分享. 在本博客的<使用Beaglebone Black的I2C(一)>中, ...

  8. EXT.JS6中的model,store,proxy的一些用法

    //one-to-one Ext.define('Address', { extend: 'Ext.data.Model', fields: [ 'address', 'city', 'state', ...

  9. HTML-loading动画1

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  10. 在Eclipse中打开Hadoop工程

    1. 安装虚拟机,我用的是VMware Workstation 12 Player 2. 在VM中安装Ubuntu,我用的镜像文件是ubuntu-15.10-desktop-amd64.iso 3. ...