Hadoop源码分析- RPC client端篇

首先摘录别人的Rpc 的一些的总结   

 1. Client 与 server 端通信采用Writable 序列化形式.因此hadoop中信息的传递 必须继承自writable 接口,writable 接口有两个方法 write 和read 
2. Client 端通过调用Call 方法,将消息序列化为writable 形式与server端通信 
3. Client 调用sendPing() 到server端.每隔一定时间,Ping时间间隔通过ipc.ping.interval 配置 。注意这里sendPing还可能是发生在读取时发生超时异常时调用此方法,方法是把InputSream用PingInputStream装饰。
4. connection方法为多路复用.多个call请求公用一个call方法,通过addCall( ) 将call 加入hash 的call队列中,但是response则单独处理,call 队列 Hashtable<Integer, Call> calls = new Hashtable<Integer, Call>() 
5. Server 端通过NIO方式将serveraddress bind到lister. 
6. Reader为读入监听到的动作key 交给doRead 去读出来 

     然后是正文

     Hadoop Rpc是源码的基础。其中RPC类是抽象工厂类,RpcEngine的具体实现类Writable/ProtoBufRpcEngine类是具体的工厂类。核心API是getServer和getProxy(或waitForProxy)方法。

    getProxy(或waitForProxy)最后是调用的Java动态代理Proxy.newProxyInstance(classloader,interfaces,invoker),而InvocationHandler实现类是ProtoBufRpcEngine.Invoker内部类。这个类的invoke方法就是客户端RPC 的proxy调用方法时真正使用的。 本文最主要就是记录本人的学习心得。

   Invoker持有的成员变量有(一个invoker只有一个client,但一个client貌似可以复用在多个invoker中,但一个invoker对应一个connection):

     remoteId这是RPC连接的id,protocolName协议名,clientProtocolVersion客户端协议版本号,还有最重要的是持有Client对象,调用方法核心就是client.call()。

  Client对象在Invoker构造函数中进行初始化,根据SocketFactory对象的不同有一个缓存的map(这个缓存的map在ClientCache对象中,由于这个对象是static final的,也就是说其实客户端的所有client都缓存在这个map中),如果是第一次,则创建。构造函数参数是
valueClass, conf,factory。第一个参数是返回值类型,一般为RPCResponseWrapper

 remoteId的创建过程也必须说明,这是由Client类的内部类ConnectionId的工厂方法来创建的,工厂方法在创建之前会根据配置文件的的重试次数和重试间隔来创建connectionRetryPolicy,然后new出来,再创建一些成员函数。

   WritableBufRpcEngine类的invoke()方法

    1.首先检查参数是不是RpcController + Message两个类型,后者是RPC请求要发送的请求。之所以参数是固定的是因为用了ProtoBuf的Protocol除了实现ClientVersion接口外,还要实现编译过的服务的BlockingInterface接口,该接口方法参数类型固定,实现类实际上是Protocol实现类的代理类。

 2 封装请求头的ProroBuf的Message,请求头的消息包括方法名,协议名,以及客户端协议的版本号。然后还会封装RpcRequestWrapper(RequestHeader,RequestMessage)。

 3.调用client。call方法。

   val = (RpcResponseWrapper) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
    new RpcRequestWrapper(rpcRequestHeader, theRequest), remoteId,
    fallbackToSimpleAuth);

  最后一个参数是表示是否采用简单认证模式。第一个参数是RPC类型,其实这版本(2.6.1)只有三种:一种Writable,一种protobuf,还有一种是测试。

 4, 然后介绍call方法,一个client有多个connection,但connection字段都相同的话Id也相同,会复用。一个connection有多个call。

     首先,创建一个Call对象,初始化它的callid和retryCount。然后创建通信的核心类Connection对象。判断这个client是否关闭,是的话抛出异常。

     其次,要获取connection。这里是同步操作,因为要操作共享资源连接池,这里是一个HashTable(不知为啥,这里已经是同步块了啊),存储connectionId到Connection的映射。没有存储就创建这样一个connection.再存储。如果需要doping就创建一个pingheader的Proto Message,Message字段包括当前clientId。

     再次,要把创建的call加当前connection的Call队列(这是一个HashTable存储callId到call的映射)。加入前判断当前连接是否应该关闭,否才加入。

    再次,就是connection的连接初始化工作。socket属性的设置;Kerborose消息的初始化;socket的bind和connect方法的调用;获取输入输出流;发送connection头部Message;获取认证的方法(并认证,这个步骤如果不成功会多次尝试);再发送一个RpcRequestMessageWrapper Message就是connection上下文信息。更新connection上次活动时间。然后启动connection的run方法,也就是接收线程(下面再分析)。

 这步完成后返回初始化完成的connection到call方法中

 再次,调用connection的connection.sendRpcRequest(call); 

   4.1.这个方法也是核心方法。首先检查connection是否关闭。其次创建一个DataOutputBuffer(继承自DataOutputStream)
来快速生成字节码写入RpcRequestHeaderProto(现在theRequestWrapper消息包含的是RequestHeaderProto头部),再写入请求体。

  4.2.接下来是发送请求体的同步代码段:先加共享锁sendRpcRequestLock(就是一个Object)。接下来用线程池来管理发送请求的异步线程(newCachedThreadPool).线程submit每个任务返回一个future。调用future.get()阻塞(之所以这样是为了合适捕捉序列化过程的错误)。每个sender的异步线程在connection的out对象上同步(说明一个connection的out会发送多个rpcRequest)依次发送前面构建的DataOutputBuffer的长度和数据本身。

  4.3.当某个sender线程发生异常时,吧它所在的connection异常关闭标志置为true,并且记录这个异常到外部类字段。把这个connection从client的connection池移除。关闭DataOutputBuffer。唤醒所有在connection对象锁上等待的线程,

  最后,调用call.wait()循环条件等待在call.done这个条件上。说明client.call每一次调用都会创建call对象,创建或者复用一个connection,而且被这个call对象阻塞到结果完成。当call.done为true时,检查call.error是本地错误还是远程错误,并各自处理。如果没有错误则返回call.rpcResponse。

  5.接下来我们介绍接收RPCResponse过程。这过程主要在Client.Connection类的run方法中,说明它是负责接收它所有call的结果。

  5.1首先循环用waitForWork检查client和connection的关闭标志才进行下面工作。call队列是否有call对象,没有则在maxIdletime的剩余时间上等待。当等待完毕时检查上面三处,满足则返回true,开始接收,否则返回false,若connection关闭则直接返回,client关闭返回中断异常,call队列为空则关闭idle connection。

  5.2接下来开始调用connection的receiveRpcResponse方法。首先检查connection标志位,其次更新connection的上次活动时间。再次获取响应头,检查它的clientId是否与当前clientId匹配,否则抛出异常结束程序。从响应头中再读取callId和请求的status状态。

  如果状态是成功,则根据client的成员变量valueClass反射建立响应消息对象,利用Proto Message的序列化方法从输入流中读取字段,设置对应call的响应消息,从calls队列中删除该call。最后如果响应消息体是ProtobufRpcEngine.RpcWrapper则进行消息的长度检查,不合格就抛出异常。把call的标志位call.done设置为true,唤醒call的发送时的阻塞线程。

  如果状态是其他,则根据header中的失败消息返回ServiceException.是失败则删除该call,并且也把call.done只为true,设置call.error为刚刚封装的remoteException。是致命错误,则关闭connection。

  注意的是接收过程中,一旦发生异常就会把connection的关闭标志置为true。

  5.3如果waitForWork返回false,client和connection的关闭,call队列长时间为空等,真正关闭这个connection。关闭的时机有两个,一是上面所述。而是建立connection失败时,关闭时会把所有的call。done置为true,并设置call。error属性,唤醒call的wait线程。

  6.最后回到call方法中,此时获取了call的rpcResponse。根据反射获取方法的返回值的Message类型,在反射获取工厂方法,获取一个返回值类型Message对象。然后用ProtoBuf api根据返回值设置返回值消息对象的具体内容,最后返回

  WritableRpcEngine的getProxy()(或waitForProxy)流程略有不同,发送的请求体是Invocation(包含方法和参数的信息)而不是ProtoBuf Message。

  


你可能感兴趣的:(源码,hadoop)