HBase源码分析2 – RPC机制:客户端

先澄清一些本文中术语的涵意

客户端 – 指的是HBase client API.提供了从用户程序连接到HBase后台服务器即Master server及Region server的功能

服务端 – 即指的是HBase的Master server 及 Region server

用户端 – 指用户程序.即对HBase client API的调用方.

本篇的主要目的是说明RPC的客户端实现.解决客户端RPC的最后两个问题

1)      传输,并发及会话控制

2)      其它的保障,如出错,重试等.

首先是RPC传输,并发.及会话

前一篇基础,已经说明HBase client可以得到HMasterInterface 和HRegionInterface的接口实例.具体参看org.apache.hadoop.hbase.client.HConnectionManager.TableServers的两个方法getMaster()和getHRegionConnection(). 从这点说,HBase的RPC与RMI是相似的,即通本地的桩(stub)对象,来传输调用,但对用户来说,这是透明的.

这个实现主要分两大步骤

  1. 生成接口的实例.这是通过代理类来实现的.
  2. 方法调用转化为序列化的socket传输.代理类将方法,参数序列化后,用socket传给服务端

第一步,这两个接口的实例,是如何实例化出来?

毫无疑问的是这两个实例是桩(stub)或称为代理,桩的创建是从org.apache.hadoop.hbase.ipc.HBaseRPC.getProxy()这个静态方法中得到的.

下面就详细的说明一下getProxy() ,它的核心是利用的java反射中的动态代理框架.调用java.lang.reflect.Proxy的静态方法newProxyInstance,它需要三个参数:

1)ClassLoader,

2)要实现的接口类(可以有多个),例如HRegionInterface,

3)最后是函数调用代理类java..lang.reflect.InvocationHandler的实例.

也就是说Proxy.newProxyInstantce的功能是将产生一个Object,这个Object实现了指定的接口,其实就是将接口与InvocationHandler的实例绑定了. 对接口中方法的调用将被转发到InvocationHandler实例上.getProxy()将生成的对象转化成VersionProtocol对象然后返回.然后再根据外部的调用将VersionProtocol转化为具体的HRegionInterface或HMasterInterface

在这步,InvocationHandler是由HBaseRPC的内部类Invoker实现的.所有的RPC调用就落实到了它的invoke方法上.invoke又利用org.apache.hadoop.hbase.HBaseClient来完成调用的传输.

第二步中,主要是

1)      序列化.

2)      Socket传输.

序列化在第一篇中,已谈到了一点,这里讲一下具体的流程

1) 调用的方法和参数被封装成Invocation(HBaseRPC内部类)对象. Invocation本身就是一个Writable对象.函数名被传化成一个编码code.可以看到Invocation内部有两个表,即方法名到一个字节值的双向的映射.首先方法名被排序了,然后它们对应的字节code从0依次增一.重载的方法编码相同,但可以根据参数不同来区分的.参数依次被序列化成HbaseObjectWritable对象.这个通过个Invocation对象,调用就可以输出成数据流或从数据流中输入.

2)这个协议的下层封装是由HBaseClient的内部类Call实现的,Call装入前述的Invocation对象,从当前的连接类HBaseClient取得一个id,这是一个自增量,然后将这个id及流的长度及Invocation通过socket连接发送出去.并同步等待(Call.Wait())返回结果.Call对象实例被放入了另一个内部类Connection的一个表calls中,是id(integer)到Call实例的映射.如果Call实例的完成标志被置,则说明结果被保存在了它的value字段中.后续反序列化的就不详述了.

以后就Socket传输的机制工作了.

Connection主要负责传输. 主要成员socket(java.net.Socket)是一条到HBase Server连接. 在这条连接上, Connection是传输调用并接收结果.它也支持并发.前面提到的calls表存放了所有正在进行传输的Call.每个Call都是从一特定的调用者线程中发起.但数据接收却不在调用者线程.

Connection本身是java.lang.Thread的子类,在它本身的线程会在socket连接建立时启动,功能是循环检查当前有没有调用Call存在,如果有就试着从socket上读取结果.并分析出结果是对应到哪个(查calls表)Call,将结果放入Call,并设置Call的完成标志,通知(call.notify())调用者线程.

最后介绍客户端的RPC容错和重试机制.

从用户端看到API并不是直接的HRegionInterface或HMasterInterface,而是HTable之类已经包装的比较高级的API.

HTable这层的功能封装是较复杂的,因为这一层的操作会分配到不同HBase服务器上,比如说,从上层看只是对一个表的查询,但在HTable这层被分解成为了对不同HRegionInterface的调用.因为一个表是有多个Region的,而不同的Region被分配到了不同的Region Server上了,而一个HRegionInterface又代表了一个Region Server.另外,为了获得Region在服务器上的分布,还要扫描Root表和Meta表等等.

本文的重点在是在HTable的业务逻辑与RPC底层机制之间部分—容错机制.

HTable 所有引起RPC调用的方法,一般都会调用到HConnectionManager.TableServers的getRegionServerWithRetries()这个方法,

以简单的HTable.delete(Delete)这个函数为例,你可以看到一个对HRegionInterface.delete的调用被封装成了一个org.apache.hadoop.hbase.client.ServerCallable对象.这个对象然后被作为参数传入了getRegionServerWithRetires方法调用.

在这个方法内部,这个ServerCallable对象被调用到,成功了就返回,否则尝试调用多次,默认10次.每次调用时的间隔还逐渐拉长.其中还将出错抛出的异常记到一个列表中,以便最终失败的时候分析原因.

考虑以下情况,当一个Region正被迁移,最初时Delete中的row key对象可能由Region Server 1来管理,但当调用发生后客户端收到了出错,因为此时这个row key从属的Region由Region Server 2来服务了.在这种重试机制下,都会经过两个流程

1)      调用ServerCallable.instantiateServer(). 这步会重置,这个row key所属的location及对应的Region server.这样就可以获得region server 2了.

2)      调用ServerCallable.call().重新发起调用.

这个机制也兼顾了一些特别情况,比如客户端可以收到一条称为DoNotRetryIOException的异常,这样客户端就不会再试了.举例来说,当执行Scan操作时,当Region迁移时,服务端会要求客户端在其它Region server上重启Scan,而不要继续.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值