简介
HBASE的官方客户端是全异步的形式做的,底层采用的netty框架,上层接口分为两类,一类是同步接口,一类是异步接口,同步接口是对异步接口的封装。
下面,我们按照剥洋葱源码阅读法,看客户端的架构。
第一步,什么是剥洋葱源码阅读法:
一般开源项目的源码组织都是非常不错的,层次分明,就像洋葱一样,我们阅读源码,其实就是看他的架构是什么样的,就像剥洋葱一样,层层剥开他的封装。
我们用.代表接口的继承,用-代表类内部依赖的其他的类。
洋葱的第一层皮:顶层接口
Connection:代表客户端向HBase集群的一个连接。
AsyncConnection:代表客户端向HBase的一个链接,方法为异步操作。
Table:代表HBASE中的一张表。
AsyncTable:代表异步操作的一张表。
Admin:代表客户端控制台。
AsyncAdmin:代表异步操作的客户端控制台。
以上六个接口,从归类上来说,其实是三个,Connection,Table,Admin,这三个接口之间的关系,是Connection作为底层支撑组件而存在,他负责与底层的通讯,而Table与Admin这两类操作接口,是借助Connection接口的功能而实现的。
注:异步接口,如果是不需要返回值的类型,会返回一个Future对象,以让上层线程能够获得任务执行的状态,如果有返回值,则会需要传入一个Consumer对象,用于异步处理返回值。
单纯看异步来说,如果是没有返回值的方法,也是有必要传入Consumer作为参数,用以操作完成后做一些收尾工作。
使用代码:
Configuration conf = HBaseConfiguration.create();
conf.set(HConstants.ZOOKEEPER_QUORUM,"xxx.xxx.xxx.xxx");
Connection connection = ConnectionFactory.createConnection(conf);
我们先从Connection操作接口说开去。
洋葱的第一层皮-1:Connection
Connection接口代表着和集群的一个连接,提供同步操作接口,也包含着集群所有的元数据信息,其实现类为ConnectionOverAsyncConnection,而这个类从名字上也能看出来,是对AsyncConnection的异步转同步的操作的封装。
洋葱的第一层皮-2 AsyncConnection
AsyncConnection接口代表着和集群的一个连接,提供异步操作的接口,主要实现类为AsyncConnectionImpl。
AsyncConnectionImpl 提供了以下几方面的内容
- 集群配置信心的查询:Configuration
- 集群Region信息的查询:AsyncRegionLocator
- 与集群各个server的连接的缓存(RpcClient)
- 与后端server进行交互时需要的组件的工厂(RpcControllerFactory,AsyncRpcRetryingCallerFactory)
这些功能在下面的Table接口的实现中都会用到。
洋葱的第2.1-1层皮:Configuration
AsyncConnection.AsyncConnectionImpl-Configuration
这个类是配置客户端的类,从上面的示例代码上来看,必须的配置为hbase zookeeper的地址。
洋葱的第2.1-2层皮:AsyncRegionLocator
AsyncConnection.AsyncConnectionImpl-AsyncRegionLocator
这个类主要的功能类是AsyncMetaRegionLocato和AsyncNonMetaRegionLocator,这两个类分别用于定位meta region的位置和no meta region的位置。
meta region的位置是放在了zk或者其他存储介质上的,而no meta region的位置放在了meta region上面,这也就是说,如果要查询no meta region的位置,要先查询meta的位置。
其余还有一个辅助类,名字叫HashedWheelTimer,这个类的主要作用为处理超时,当获得region的请求时间超时时,这个类负责以异常的形式结束现有请求。
洋葱的第2.1-2-1层皮:AsyncMetaRegionLocator
AsyncConnection.AsyncConnectionImpl-AsyncRegionLocator-AsyncMetaRegionLocator
这个类的主要实现是委托给了AsyncRegistry,其主要实现类是ZKAsyncRegistry,因为hbase的配置中心使用的是zk,当然,如果你喜欢的话,也可以给封一层etcd的。
剩余的两个AtomicReference是做的缓存,避免客户端频繁读取zk把zk给读挂了。
洋葱的第2.1-2-1-1层皮:ZKAsyncRegistry
这个类就简单了,其实是读取zk上面的配置,拿到手后返回给上一层。
洋葱的第2.1-2-2层皮:AsyncNonMetaRegionLocator
这个类是从meta表中检索出table的region location的类,使用的是客户端的scan操作,这里面会有一个循环依赖的问题,即当你从上层检索一个表中的内容的时候,会先使用同样的上层sacn操作检索meta表,这就导致了循环依赖问题的产生。
洋葱的第2.1-3层皮:RpcClient
RpcClient是一个接口,他的作用像一个工厂,用来生成BlockingRpcChannel和RpcChannel(非Blocking)。
BlockingRpcChannel和RpcChannel是proto的rpc框架与后端的通信接口(为什么必须要兼容proto的rpc接口啊,感觉好鸡肋,自己写更灵活)。
RpcClient的主要实现类为NettyRpcClient,在这中间,还有一个AbstractRpcClient。
AbstractRpcClient主要负责发送数据的准备工作,并缓存RpcConnection对象,剩余的工作交付给RpcConnection去做,而如何获得RpcConnection,是委托给子类NettyRpcClient实现。
洋葱的第2-3-1层皮:Codec,CompressionCodec
这些是用于生成RpcConnection的,用途是将生成的字节流进行编码和压缩。
洋葱的第2-3-1层皮:PoolMap<ConnectionId, T>
这个map是用于缓存RpcConneciton的,防止生成多次对server端的连结。
洋葱的第一层皮-3:Table
Table为Hbase同步操作table的接口,实现类为TableOverAsyncTable,它是基于异步AsyncTable实现的,上面说到,整个Hbase的官方客户端为全异步架构,Table只是封装了一层底层的AsyncTable罢了。
洋葱的第一层皮-4:AsyncTable
AsyncTable的实现类有两个。
- AsyncTableImpl
- RawAsyncTableImpl
洋葱的第4.1层皮:AsyncTableImpl
AsyncTableImpl这个类是为了对scan的result做异步处理做的,为了实现这个功能,必须要传递进去一个ExecutorPool,当scan返回result后,由这个线程池处理,其余的工作,全部委托给了RawAsyncTableImpl。
洋葱的第4.2层皮:RawAsyncTableImpl
RawAsyncTableImpl这个类负责处理全部的Table相关的核心逻辑,到这里,洋葱的第一层皮就剥掉了。
以上所有的类,即洋葱的第一层皮,使用的数据结构载体,即model类是一致的,即面向用户的Scan,Put,Get等model类。
再向下面,就是洋葱的第二层皮,这层皮的接口需要的数据,和第一层就不一样了。
以上所说的所有的操作,我们只是看到了一个大概的,对用户比较有好的异步接口实现,真正的IO请求逻辑在这个类里面展开。
洋葱的第4.2-1层皮:AsyncRpcRetryingCaller
AsyncRpcRetryingCaller的功能为控制向后端HBASE发送请求,从类的名字我们就能够看出来,这个主要是控制重复发送请求的类,Table接口后续的操作是依托AsyncRpcRetryingCaller及其实现类实现的,每次上层接口进行RPC请求,都会创建一个新的AsyncRpcRetryingCaller对象用以向后端进行请求,而对象的内部存储着所有需要向后端传递的信息。
AsyncRpcRetryingCaller对上层提供的方法为call(),没有参数,他也是上面所有组件的协调器,上面所说的所有的组件,都是他内部的属性。
这是一个抽象类,实现了一些基础的重试请求功能。
它的子类实现了不同的发送请求的功能,子类如下
- AsyncSingleRequestRpcRetryingCaller:实现了单个请求,例如get,put,delete的请求。
- AsyncAdminRequestRetryingCaller:admin控制请求的。
- AsyncMasterRequestRpcRetryingCaller:与master请求的。
- AsyncRegionReplicaReplayRetryingCaller:region请求。
这些子类都封装了不同的请求模式的请求功能,即AsyncRpcRetryingCaller中的抽象方法doCall().接下去的流程就到了thrift的rpc框架接口了。
洋葱的第4.2-1-1层皮:stub
stub是protobuf的一个通用rpc接口框架。他定义了整个RPC的顶层接口,接口有。
- Interface 层,这一层是给人使用的时候提供便利的,定义了很多不同的接口方法,当然,这些方法是描述文件里面定义的,这里只是给翻译成了java代码。
- RpcChannel层,这一层就是纯传输数据使用,上面定义的所有的接口,在这一层都被汇集到一个方法上面,向后方的server传递,从上面介绍RpcClient看,这个类的工厂类是RpcClient的实现类做的。
但是,protobuf只不过是定义了接口与如何序列化,真正的IO通信实现还是要自己写的,这就到了洋葱的第四层-RpcConnection。
洋葱的第4.2-1-1-1层皮:RpcConnection
我们从上面NettyRpcClient的介绍能看到,RpcConnection的创建是通过RpcClient的底层类实现的,通过AbstractRpcClient缓存RpcConnection。
RpcConnection是最终发送数据给后端Server的接口,发送的方法为
public void sendRequest(final Call call, HBaseRpcController hrc)
主要实现类为NettyRpcConnection,使用netty将数据传输给后端server。NettyRpcClient会生成NettyRpcConnection。
洋葱的第4.2-1-1-2层皮:Call对象
Call类是RpcConnection接口的参数类,内部有所有这次IO通讯所需要的信息,例如需要向后端传递的数据Message(protobuffer可序列化的顶层接口),本次Call的id,调用的是Rpc的哪个方法RpcClient,本次调用的callback信息,Span信息等。
洋葱的第4.2-1-1-3层皮: HBaseRpcController
HbaseRpcController是继承于protobuf的RpcController,protobuf的RpcController是用于控制单次Rpc请求的超时,记录异常等信息的,但在HbaseRpcController中,新增了携带Cell的功能,包括发送和接收,这是用于自定义压缩及编码需求的,但这项实现笔者认为比较怪异。
洋葱的第5层皮:Admin
不用说了,铁定是一层AsyncAdmin的包裹。
洋葱的第6层皮:AsyncAdmin
AsyncAdmin实现类有两个,AsyncHBaseAdmin和RawAsyncHbaseAdmin。
洋葱的第6.1层皮:AsyncHBaseAdmin
和上面的AsyncTable一样,包了一层pool。
洋葱的第6.2层皮:RawAsyncHBaseAdmin
这个的实现,和RawAsyncTableImpl是一样的,借用RetryingCaller实现后端通信,区别在于找的server的不一致,HMaster的server地址是设置在ZK上的,通过上面所说的AsyncConnectionImpl获得(这个类依赖ZKAsyncRegistry从ZK上获得)。