标签归档:hbase

hbase的行锁与多版本并发控制(MVCC)

MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是,把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能。

HBase正是通过行锁+MVCC保证了高效的并发读写。

为什么需要并发控制

HBase系统本身只能保证单行的ACID特性。ACID的含义是:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

传统的关系型数据库一般都提供了跨越所有数据的ACID特性;为了性能考虑,HBase只提供了基于单行的ACID。

下面是一个hbase并发写的例子。

原始数据如下
mvcc

Apache HBase Write Path一文可以知道hbase写数据是分为两步:
1. 写Write-Ahead-Log(WAL)文件
2. 写MemStore:将每个cell[(row,column)对]的数据写到内存中的memstore

写写同步

假定对写没有采取并发控制,并考虑以下的顺序:

mvcc

最终得到的结果是:

mvcc

这样就得到了不一致的结果。显然我们需要对并发写操作进行同步。
最简单的方式是提供一个基于行的独占锁来保证对同一行写的独立性。所以写的顺序是:

  • (0) 获取行锁
  • (1) 写WAL文件
  • (2) 更新MemStore:将每个cell写入到memstore
  • (3) 释放行锁

读写同步

尽管对并发写加了锁,但是对于读呢?见下面的例子:
mvcc

如果在上面的图中红线所示的地方进行读操作,最终得到的结果是:
mvcc

可见需要对读和写也进行并发控制,不然会得到不一致的数据。最简单的方案就是读和写公用一把锁。这样虽然保证了ACID特性,但是读写操作同时抢占锁会互相影响各自的性能。

MVCC算法

HBase采用了MVCC算法来避免读操作去获取行锁。

对于写操作:

  • (w1) 获取行锁后,每个写操作都立即分配一个写序号
  • (w2) 写操作在保存每个数据cell时都要带上写序号
  • (w3)
--> 阅读全文

hadoop集群升级到yarn遇到的数据本地化(data-locality)很低的问题分析

最近将hadoop集群从hadoop-2.0.0-mr1-cdh4.1.2升级到hadoop-2.0.0-cdh4.3.0,遇到了一些任务(如scan hbase table)数据本地化data-local很低的问题,原因就是前篇文章中说到的ipv4和ipv6导致的机器名长短问题。本文是对整个过程的分析。

我们的yarn集群采用的是capacity-scheduler调度,data-local很低的原因是:机器名不一致,导致分配container时无法匹配上。在yarn内部(NodeManager)用的机器名是 test042097.sqa.cm4 这种短的,而job的split信息(如scan hbase)里面带的机器名是 test042097.sqa.cm4.site.net 这种完整机器名。

在 ResourceManager 分配container的地方:(RMContainerAllocator.java:967行)

代码里面是按照 host, rack, any 三种方式顺序匹配的,在 mapsHostMapping 里面存的是资源的请求,<hostname, List<TaskAttemptId>>,意思是对请求在某台机器上启动几个task。hostname是来自于job输入的split信息,比如扫描hbase表的job,split信息里面就有region所在的机器信息。

在hbase里面,用到的机器名都是 test042097.sqa.cm4.site.net 这种带了 site.net 后缀的,所以mapsHostMapping的key就是 test042097.sqa.cm4.site.net。而Yarn的NodeManager所用的机器名都是 test042097.sqa.cm4 这种不带后缀的,也就是 allocated.getNodeId().getHost();的值。因此机器名是没法匹配上的。


后来,我们的集群采用斯盛同学提出的改法:“每台机器一个rack”后,rack local比例提高了很多,此时的rack local其实就是data locale。

这样改后,上面的代码中 RackResolver.resolve(host).getNetworkLocation(); 这个会调用集群配置的rack脚本,会读取一个rack文件信息:

所以,通过search042097.sqa.cm4可以映射到 /rack/4,与 mapsRackMapping 中的数据就匹配上了。(mapsRackMapping中存有请求的资源rack信息,yarn中的资源请求是超额请求的,用一个申请三个,主机+机架+any)

YARN中的NodeManager获取hostname的地方,是在ContainerManagerImpl.java:243行

底层调用的是jdk的 方法,得到的是 search042097.sqa.cm4 这种短主机名。

而普通map … --> 阅读全文

多线程下使用htable,当region server挂了client端重试很长时间

今天遇到一个问题,hbase集群(版本是hbase 0.94.5)中有一台Region Server由于硬件问题宕机,但是往这台RS机器写数据的应用程序不停的重试了近40分钟才重试成功。按说,在正常情况下,hbase几分钟最多十几分钟能转移挂掉的RS机器上的数据,应用程序很快就能发现问题,不用重试40分钟这么久。

1. master日志

查看master日志,发现从17:20发现test032034.cm1这台机器有问题,经过spilt log等操作,到17:38分这台机器上的region已经重新分配到了其他机器上。

2013-06-05 17:20:24,014 DEBUG org.apache.hadoop.hbase.master.AssignmentManager: based on AM, current region=.META.,,1.1028785192 is on server=test036024.cm1,30020,1370358473264 server being checked: test032034.cm1,30020,1370358473360
……
2013-06-05 17:38:11,570 INFO org.apache.hadoop.hbase.master.handler.ServerShutdownHandler: Reassigning 49 region(s) that test032034.cm1,30020,1370358473360 was carrying (skipping 0 regions(s) that are already in transition)
……
2013-06-05 17:38:14,614 INFO org.apache.hadoop.hbase.master.handler.ServerShutdownHandler: Finished processing … --> 阅读全文

HTablePool的使用和源码分析

HTable与HTablePool

HTable是HBase的客户端API,用来实现对hbase表的增加(Create)、查询(Retrieve)(重新得到数据)、更新(Update)和删除(Delete),简称CRUD操作。但是HTable并不是线程安全的。在HTable的API说明中写道:

HTable适合对单表操作,对读或写操作都不是线程安全的。对于写操作(Put或Delete),如果多线程共享一个HTable实例,写缓冲区可能会被破坏。对于读操作,一些被scan使用的字段同时被多个线程共享,如果此时有Get操作,不能保证是安全的。

创建HTable的开销较大,多个HTable实例可共享一个Configuration。

文档中建议使用HTablePool。它就是个对象池,可以复用HTable实例,使用的时候通过getTable方法获取一个HTable对象,然后可以进行各种 scan/get/put/delete 等操作,使用完调用close()方法将htable对象归还到池中。

HTablePool的使用

一个典型的使用HTablePool的代码例子:

HTablePool分析

maxSize

在HTablePool中,针对每个表,都用PoolMap<string, htableinterface=””> tables对象存储了一个表名到pool的映射。maxSize指的就是这个pool的大小,表示每一个表的最大引用个数。在HTablePool中的多个HTable实例会共享同一个Configuration。 当调用getTable方法时,在findOrCreateTable方法中会判断当前是否有可用的htable,否则就通过HTableInterfaceFactory创建一个。

返回的hable会被包装成一个PooledHTable,PooledHTable是htable的代理,里面唯一不同的是close()方法里调用了returnTable(table)来将htabe归还到池中。

如果tables.size>=maxsize,会移除掉这个HTable实例,而releaseHTableInterface调用的就是HTable的close方法,close方法会调用flushCommits(),强制flush write buffer。

这是一个需要注意的地方:hbase中有个autofulsh的参数,表示是否对client端请求进行缓存。

如果autoflush=false,当客户端提交delete或put请求时,该请求会在客户端缓存,直到数据超过2M(由hbase.client.write.buffer决定)或用户执行了hbase.flushcommits()时才向regionserver提交请求。因此即使htable.put()执行返回成功,也并非说明请求真的成功了。假如还没有达到该缓存而client崩溃,该部分数据将由于未发送到regionserver而丢失。这对于零容忍的在线服务是不可接受的。

所以hbase默认的配置是autoflush=true,表示每次请求都会发往regionserver, 而regionserver接收到请求后第一件事就是写hlog,这样能保证数据不会丢,但是对磁盘IO要求比较高,写入速度会较低。

使用HTablePool的htable对象,调用close()方法会触发flushCommits(),如果有某些应用将autoflush=false想提升写入速度会失效。

PoolType

HTablePool提供了三种类型的pool:Reusable, ThreadLocal, RoundRobin,默认的是reusable。

  1. ReusablePool内部是用ConcurrentLinkedQueue来保存htable实例,保证了线程安全,主要用于多线程复用。如果调用get()方法,内部调的是queue的poll(),会从当前队列头部取出一个htable实例或者返回null。
  2. ThreadLocalPool用ThreadLocalPool来实现pool和使用它的线程进行绑定,因此pool的大小和线程个数相同。
  3. RoundRobinPool内部是用CopyOnWriteArrayList来保存htable实例。它是为了平衡对htable资源的使用,每次都会返回不同的htable实例。但是在HTablePool的构造函数中,不能创建这种类型的pool。

HTableInterfaceFactory

此外,HTablePool还支持通过HTableFactory来实现自定义的HTable。