hbase Java client(Release 1.0)

Hbase Java Client简介

概述以及架构

我们在使用hbase的时候,数据操作都是和regionserver直接通信操作,hbase的Java客户端将这些操作都封装在HTable类中,对外我们使用的所有操作都是直接使用HTable的api来直接操作,HTable的基本操作如下:

get、get list、put、put list、delete、delete list、append、Increment、checkAnd***、scan 。

hbase client架构图如下:

虚线中的部分是线程安全的,在整个jvm client中可以维持一份来供多线程使用,避免资源的重载,资源重建等耗时操纵。

 

HTable

Htable是对外的数据操作接口,提供了hbase基本所有的数据操作方法, htable中提供了buffer的功能(BufferedMutator),当buffer达到一定的size或者num后,会自动后台线程flush到相应的regionserver。

目前这个buffer只针对put(put list)操作并且需要关闭自动提交,可以异步提交put请求并并flush,其它操作都是同步完成,有些单个的请求是直接同步发送rpc请求,批量的操作涉及到多个regionserver通信的操作,会分组做成runnable,提交到thread pool并行执行,待所有执行完成后返回。

Htable中目前看除了带buffer的put操作,其它的操作其实还都是线程安全的,因为其它操作基本上没有太多共用的属性,而且操作大头其实都是聚集在connection(regionlocation cache、rpc socket)中,而connection中的操作都是线程安全的。

htable的构造方法如下:

需要connection和相应的配置,可以自定义executorService,也可以直接从connection.getTable(TableName)方法获得,创建这些table其实是没有太多开销的,底层的connection共用,如果executorService为null则共用connection中的executorService,建议这样。

ps:在1.0以前的HTable中会有用configuration 和 tableName作为构造参数来初始化HTable的方法,这样其实会在初始化的时候重新create一个connection对象,connection相关的resource也会重新创建,关闭的时候一并关闭了,1.0之后这些方法都打上过时标识了,以后不建议使用。

Connection

整个client中最重要的一部分,可以理解为和整个hbase集群的连接,管理着两部分共用的东西,当然这两部分的初始化也是最耗时间的:

1、region cache,每个表的region以及region handle的数据范围,所在的regionserver等信息,这部分数据是在用到时候会更具row去meta表中查到相对应的信息(每次查询是一张表一张表的数据缓存的),然后就一直缓存起来,直到使用这个cache出现RegionNotOnline、moved、notexsit等异常从regionserver反馈回来的时候,再更新cache,每个region都有一个唯一的id,已开这个id去定位唯一的region的,这个id在split、merger等操作后都是会改变的,所以不会出现在split和merger操作后的歧义。

2、连接到各个regionserver的通信socket,具体在使用上的体现就是RpcClientImpl,每个regionserver一个socket连接 。

这两部分资源在使用时候都是线程安全的,所以在整个client的jvm中维护一个connection即可,也恰恰是这两部分的cache/连接在整个数据通信中是最耗时的两部分操作。

关于Connection的使用以及初始化,在1.0版本以前,所有的Connection由ConnectionManage来管理,保存在一个静态Map中,可以通过全局的get方法得到,1.0以后这些方法还存在,但是都已加上过时标志,建议用户通过ConnectionFactory来创建链接并管理、使用、销毁。

所以在并发的环境下建议的使用代码就是:

Connection connection = ConnectionFactory.createConnection(config); //创建后的connection需要使用者保存维护,并在程序最后结束的时候destroy掉,比如jvm stop hook中

//具体的使用如下

Table table = connection.getTable(TableName.valueOf("table1"));

table.close();

由于connection是公用的,所以在create、destroy HTable的时候其实开销并不大,从目前1.0的api以及api过时标注来看,建议的也是使用这自己初始化connection并管理,其余的HTable从其上面衍生出来来使用。

 

ExecutorService

其实就是java中的ExecutorService,目前主要用于批量(flush put buffer也算是批量)执行的时候,因为批量执行时,其中的请求会发送到多个regionserver上,所以使用线程池的方式来并发执行这些rpc请求。

 

Hbase rpc interface

hbase client在使用的过程中数据的操作都是直接跟regionserver来交互的,hbase rpc接口(包括regionserver)目前都是使用protobuf来定义interface和VO数据,这样做的目的是为了做多语言的兼容,目前regionserver的rpc接口如下(省略了bulkload和coprocess的接口):

service ClientService {

  //单个的get接口

  rpc Get(GetRequest)

    returns(GetResponse);

  //批量处理基本的操作(get/put/delete/append/incerament都支持),这个和Multi的区别是,这个支持整个操作原子性,所以类似于checkand***/单个的append、increment的都会走到这个

  rpc Mutate(MutateRequest)

    returns(MutateResponse);

  //scan接口,这个接口在调用的时候第一次是打开一个scanner并产生一个scanner id,后续再根据这个scanner的squence id去一批批的取数据。

  rpc Scan(ScanRequest)

    returns(ScanResponse);

  //如上面介绍,这个不支持原子操作的,hbase的原子操作靠的是mvcc来实现的

  rpc Multi(MultiRequest)

    returns(MultiResponse);

}

PS:最初的hbase版本沿用了hadoop的writable序列化接口,在0.96的rpc系统中写了一套兼容writable和pb的rpc,在0.98开始全面移除了对于writable的兼容,这种方式对于向后兼容以及跨语言是个很好的支持。

 

Hbase RPC底层

Hbase rpc底层通信类似于hadoop的一套rpc,异步rpc client,其实说白了就是一套,只不过是两份代码。

序列化目前全部采用的是pb来做的序列化,没有了以前的hadoop中的writable那一套,其底层的socket通信采用的是socket的简单同步通信方式,在这个socket之上做了sender 和 receiver(其实就是两个线程了),来达到异步的效果,简单的流程就是:

1、client 发送call请求(每个call都会被分配一个squenceId);

2、sender接收到缓存起来,然后client在call上wait(timeoutInMilli),等待被唤醒,在等待超时后会抛出异常,可以认为请求等待超时,这一步其实可以做成异步的形式,不wait,只是发送请求。

3、sender中的主线程不停的发送自己缓存起来的call,发送的时候会在底层的socket上加锁,所以这一步是线程原子操作;

4、receiver不停的接收服务端的返回,去取response然后根据call id找到相应的call并唤醒相应的call ;

如上面的第2步所示,底层的rpc其实是留了异步的接口,但是hbase上层在调用的时候才用了wait来达到同步调用的效果。

 

Hbase操作分类

put、put list,这两个操作在htable关闭自动刷新的时候,数据会先缓存到buffer中,然后buffer溢出后在批量flush,使用executorservice的线程pool来并行写数据,其最终调用的是regionserver的Multi(MultiRequest)接口。

 

get、delete这两个操作会直接发送到相应的regionserver,所有的操作都是在调用线程中完成,调用底层的rpc发送接口,get调用Get(GetRequest),delete调用Multi(MultiRequest)

 

get list、del list最终都会转换为hbase的batch方法,底层再调用Multi(MultiRequest)接口,只不过和put处理流程一直,因为可能会涉及到多个regionserver,所以会将所有的请求按照regionserver分组,然后并发处理(使用executorservice中的thread pool)每个regionserver的请求,只不过batch是同步调用的,需要等待所有的调用over后才会主方法返回。

 

Append、increament以及一些CAS操作也是在直接发送到相应的rs,所有的操作都是在调用线程中完成,最终调用底层的Mutate(MutateRequest)接口。

 

Scan是一个比较特殊的操作,scan的数据是分批量从regionserver取回的,第一次调用Scan(ScanRequest)方法的时候(注意scan操作会将start-end分到相应的region上,从第一个region开始遍历,批量取回数据),会在regionserver中打开一个scanner生成唯一的scannerid并记录读取offset等信息,下次调用的时候会从上次的offset开始取数据,知道结束,然后客户端会飘到下一个region上。整个scan是一个客户端和server配合完成的过程,每次请求会有scannerid和请求的squenceid,服务端会根据这些判断是请求是否合法,是否是网络抖动引起的重复请求等行为,而且在regionserver上会对每个scanner做超时检测,超时的时候移除相应的scanner。

 

关于batch操作时候的异常处理

在batch处理 (底层调用Multi(MultiRequest)方法) 的时候,这时候和regionserver通信时允许部分操作失败的,在收到失败异常后会对异常进行分类,一些逻辑如下:

1、RegionTooBusyException、RegionTooBusyException的话,不处理

2、RegionMovedException 会更新cache ,因为这个异常会带回新的region的位置rs信息 

其它异常的会,会先判断一些现在的cache对应的还是不是我之前用的那个region对应关系了,是的话再清理, 因为有可能我用的过程中已经被别人更新了。

3、DoNotRetryIOException时候才不会再重试了 ,对应的操作会被移除并不被重试。

这一块其实应该把异常处理能留出一个用户自定义的接口,有些环境中其实是需要快速失败的。

在重试一定的次数(hbase.client.retries.number配置,默认是31)或者一定的时间(hbase.rpc.timeout配置,默认是60s)后,则失败。

 

关于async hbase 

Async hbase是OpenTSDB 项目中,作者重写的一个hbase客户端

开源项目的主页:http://opentsdb.net/  

今年的HBaseCon上作者的一个演讲ppt

http://www.slideshare.net/HBaseCon/operations-session-3-49043534

简单读了下代码,几点总结如下:

1、线程安全,一个HbaseClient使用整个jvm中。

2、完全异步提交数据,客户端缓存数据、另外线程刷新数据,失败异常等使用callback的形式回调。

3、完全没有用hbase client中的代码,rpc的通信头构造都是自己构造的,数据刷新具体逻辑没细看,感觉掐掉了很多hbase原生client的重试、异常检测等一系列的fault-tolerant功能, 为了性能。

4、掐掉了原生hbase的一些操作,目前看只支持put、del和相应的cas,scan,get这些操作。

5、底层通信使用的是netty的nio style client,完全的异步行为。hbase(hadoop)的rpc client是一个模拟的异步socket, 调用send的时候,其实是有个sender的线程把你的call缓存了,这个线程的工作就是一直从queue中取出call写socket,receiver线程,一直从inputStream读数据,读到response后解码并,更细call为complement状态,同步socket一种类异步的封装。

 

1.0的一些新特性

region replicas

region副本,每个region会在多个regionserver上打开,但是只有一个主region接收写请求,其它的都是readonly, 这个region可以使用更新到hdfs上的文件数据,但是memstore中的数据会延后,对于大量读但对一致性要求不高的场景,不过这个目前还是个实验性质的feature。

https://issues.apache.org/jira/secure/attachment/12616659/HighAvailabilityDesignforreadsApachedoc.pdf

动态conf

就是动态修改并加载配置,不需要重启集群。

 

posted @ 2015-09-21 16:49  xiao晓  阅读(1580)  评论(0编辑  收藏  举报