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. 关于后台返回excel文件的问题

    一般情况ajax请求只能获取解析非流文件类型,而excel是流文件类型,这个时候获取到的数据会是一串乱码的字符串 想要下载这个excel文件,通过form表单模拟的方式可以解决 var form = ...

  2. 在vue项目当中使用sass

    需要分别安装node-sass 和 sass-loader;可以不需要ruby; webpack当中配置 { test: /\.vue$/, loader: 'vue-loader', options ...

  3. 主机ping不通虚拟机,但是虚拟机能ping通主机

    一.虚拟机网络连接方式选择Nat 二. 关闭Linux防火墙命令:service iptables stop / service firewalld stop 查看Linux防火墙状态命令:servi ...

  4. poj 3293 Rectilinear polygon

    Rectilinear polygon Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 2125   Accepted: 24 ...

  5. [LeetCode] Edit Distance 字符串变换为另一字符串动态规划

    Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2 ...

  6. usb_control_msg参数详解【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3243059.html usb_control_msg() struct usb_ctrlrequest|- ...

  7. gcc 内置函数

    关于gcc内置函数和c隐式函数声明的认识以及一些推测   最近在看APUE,不愧是经典,看一点就收获一点.但是感觉有些东西还是没说清楚,需要自己动手验证一下,结果发现需要用gcc,就了解一下. 有时候 ...

  8. ASP.NET路由应用及IIS配置(非MVC)

    一.前后台代码: Global.cs: using System.Web.Routing; ... void Application_Start(object sender, EventArgs e) ...

  9. 转Yii框架radioButtonlist水平横排及去除除换行符号

    横排: echo $form->radiobuttonlist($model, ‘type’,$arrtype,array(‘template’ => ‘<li style=”dis ...

  10. Error: spawn xxxx ENOENT原因与解决

    背景: npm 运行项目时出现了该问题 原因: path环境变量配置不当,导致无法找到指定的程序,如Error: spawn cmd.exe ENOENT,出现该问题的原因是因为没有将%SystemR ...