Hadoop3.2.1 【 HDFS 】源码分析 : RPC原理 [八] Client端实现&源码

这篇文章主要写 Hadoop RPC Client 的设计 与实现 . 在讲解的时候, 以 ProtobufRpcEngine为实例, 然后分步进行叙述.

 

 

一.Client端架构

Client类只有一个入口, 就是call()方法。 代理类会调用Client.call()方法将RPC请求发送到远程服务器, 然后等待远程服务器的响应。 如果远程服务器响应请求时出现异常, 则在call()方法中抛出异常。 

  • 在 call 方法中先将远程调用信息封装成一个 Client.Call 对象(保存了完成标志、返回信息、异常信息等),然后得到 connection 对象用于管理 Client 与 Server 的 Socket 连接。

  • getConnection 方法中通过 setupIOstreams 建立与 Server 的 socket 连接,启动 Connection 线程,监听 socket 读取 server 响应。

  • call() 方法发送 RCP 请求。

  • call() 方法调用 Call.wait() 在 Call 对象上等待 Server 响应信息。

  • Connection 线程收到响应信息设置 Call 对象返回信息字段,并调用 Call.notify() 唤醒 call() 方法线程读取 Call 对象返回值。

 

 

 

 

二.Client端创建流程

 

下面是创建Client端的代码.协议采用proto, 所以产生的RpcEngine是 ProtobufRpcEngine. 所以接下来的文章是以ProtobufRpcEngine为蓝本进行源码分析.

我只放了Server端, 详细的代码请查看:

 Hadoop3.2.1 【 HDFS 】源码分析 : RPC原理 [六] ProtobufRpcEngine 使用

 

     public static void main(String[] args) throws Exception {

        //1. 构建配置对象
        Configuration conf = new Configuration();

        //2. 设置协议的RpcEngine为ProtobufRpcEngine .
        RPC.setProtocolEngine(conf, Server.MetaInfoProtocol.class,
                ProtobufRpcEngine.class);


        //3. 拿到代理对象
        Server.MetaInfoProtocol proxy = RPC.getProxy(Server.MetaInfoProtocol.class, 1L,
                new InetSocketAddress("localhost", 7777), conf);

        //4. 构建发送请求对象
        CustomProtos.GetMetaInfoRequestProto obj =  CustomProtos.GetMetaInfoRequestProto.newBuilder().setPath("/meta").build();

        //5. 将请求对象传入, 获取响应信息
        CustomProtos.GetMetaInfoResponseProto metaData = proxy.getMetaInfo(null, obj);

        //6. 输出数据
        System.out.println(metaData.getInfo());

    }    

 

上面的代码, 主要是分三部分. 

1.构建配置对象&设置RpcEngine引擎

2.获取代理对象

3.设置请求参数&通过代理对象请求.

4.处理结果

第一条和第二条,我就不细说了. 这个很简单,就是使用proto定义一个协议, 绑定到RPC.Builder的实现对象里面.

我们直接看这段, 获取代理对象.

 Server.MetaInfoProtocol proxy = RPC.getProxy(Server.MetaInfoProtocol.class, 1L,
                new InetSocketAddress("localhost", 7777), conf);

也是就是通过RPC.getProxy方法获取协议的代理对象.

   /**
    * Construct a client-side proxy object with the default SocketFactory
    * @param <T>
    * 
    * @param protocol 协议
    * @param clientVersion 客户端的版本
    * @param addr  请求地址
    * @param conf 配置文件
    * @return a proxy instance
    * @throws IOException
    */
   public static <T> T getProxy(Class<T> protocol,
                                 long clientVersion,
                                 InetSocketAddress addr, Configuration conf)
     throws IOException {

     return getProtocolProxy(protocol, clientVersion, addr, conf).getProxy();
   }

接续看,这里面就一句getProtocolProxy,加断点一直跟进

 /**
   * Get a protocol proxy that contains a proxy connection to a remote server
   * and a set of methods that are supported by the server
   *
   * @param protocol protocol
   * @param clientVersion client's version
   * @param addr server address
   * @param ticket security ticket
   * @param conf configuration
   * @param factory socket factory
   * @param rpcTimeout max time for each rpc; 0 means no timeout
   * @param connectionRetryPolicy retry policy
   * @param fallbackToSimpleAuth set to true or false during calls to indicate if
   *   a secure client falls back to simple auth
   * @return the proxy
   * @throws IOException if any error occurs
   */
   public static <T> ProtocolProxy<T> getProtocolProxy(Class<T> protocol,
                                long clientVersion,
                                InetSocketAddress addr,
                                UserGroupInformation ticket,
                                Configuration conf,
                                SocketFactory factory,
                                int rpcTimeout,
                                RetryPolicy connectionRetryPolicy,
                                AtomicBoolean fallbackToSimpleAuth)
       throws IOException {
    if (UserGroupInformation.isSecurityEnabled()) {
      SaslRpcServer.init(conf);
    }
    return getProtocolEngine(protocol, conf).getProxy(protocol, clientVersion,
        addr, ticket, conf, factory, rpcTimeout, connectionRetryPolicy,
        fallbackToSimpleAuth, null);
  }

debug界面是这样的:

 

核心的是这句 

return getProtocolEngine(protocol, conf).getProxy(protocol, clientVersion,
        addr, ticket, conf, factory, rpcTimeout, connectionRetryPolicy,
        fallbackToSimpleAuth, null);

首先通过 getProtocolEngine 获取RPC Engine [ ProtobufRpcEngine] ,

// return the RpcEngine configured to handle a protocol
  static synchronized RpcEngine getProtocolEngine(Class<?> protocol,
      Configuration conf) {
    //从缓存中获取RpcEngine ,
    // 这个是提前设置的
    // 通过 RPC.setProtocolEngine(conf, MetaInfoProtocol.class,ProtobufRpcEngine.class);

    RpcEngine engine = PROTOCOL_ENGINES.get(protocol);
    if (engine == null) {

      //通过这里 获取RpcEngine的实现类 , 这里我们获取的是 ProtobufRpcEngine.class
      Class<?> impl = conf.getClass(ENGINE_PROP+"."+protocol.getName(),
                                    WritableRpcEngine.class);

      // impl  : org.apache.hadoop.ipc.ProtobufRpcEngine
      engine = (RpcEngine)ReflectionUtils.newInstance(impl, conf);
      PROTOCOL_ENGINES.put(protocol, engine);
    }
    return engine;
  }

 

然后再调用 ProtobufRpcEngine的 getProxy方法.将协议,客户端的版本号. socket地址, ticket , 配置文件, socket 的创建工厂对象[ StandardSocketFactory ] , PRC 服务的超时时间, connetion的重试策略,以及权限等信息,传入.

@Override
  @SuppressWarnings("unchecked")
  public <T> ProtocolProxy<T> getProxy(Class<T> protocol, long clientVersion,
      InetSocketAddress addr, UserGroupInformation ticket, Configuration conf,
      SocketFactory factory, int rpcTimeout, RetryPolicy connectionRetryPolicy,
      AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
      throws IOException {


    //构造一个实现了InvocationHandler接口的invoker 对象
    // (动态代理机制中的InvocationHandler对象会在invoke()方法中代理所有目标接口上的 调用,
    // 用户可以在invoke()方法中添加代理操作)
    final Invoker invoker = new Invoker(protocol, addr, ticket, conf, factory,
        rpcTimeout, connectionRetryPolicy, fallbackToSimpleAuth,
        alignmentContext);

    //然后调用Proxy.newProxylnstance()获取动态代理对象,并通过ProtocolProxy返回
    return new ProtocolProxy<T>(protocol, (T) Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker), false);
  }

在getProxy 这个方法中. 主要是分两步, 

1. 构造一个实现了InvocationHandler接口的invoker 对象 (动态代理机制中的InvocationHandler对象会在invoke()方法中代理所有目标接口上的 调用, 用户可以在invoke()方法中添加代理操作

2.调用Proxy.newProxylnstance()获取动态代理对象,并通过ProtocolProxy返回

我们先看Invoker的创建.

 

    private Invoker(Class<?> protocol, InetSocketAddress addr,
        UserGroupInformation ticket, Configuration conf, SocketFactory factory,
        int rpcTimeout, RetryPolicy connectionRetryPolicy,
        AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
        throws IOException {
      this(protocol, 
            Client.ConnectionId.getConnectionId(addr, protocol, ticket, rpcTimeout, connectionRetryPolicy, conf), conf, factory);
      this.fallbackToSimpleAuth = fallbackToSimpleAuth;
      this.alignmentContext = alignmentContext;
    }

主要是: 

this(protocol,  Client.ConnectionId.getConnectionId(addr, protocol, ticket, rpcTimeout, connectionRetryPolicy, conf), conf, factory);

我们先看

 Client.ConnectionId.getConnectionId(addr, protocol, ticket, rpcTimeout, connectionRetryPolicy, conf), conf, factory)

这里会调用getConnectionId方法 构建一个Client.ConnectionId对象.

ConnectionId :  这个类 持有 请求地址 和 用户的ticketclient 连接 server 的唯一凭证 :  [remoteAddress, protocol, ticket]

/**
     * Returns a ConnectionId object. 
     * @param addr Remote address for the connection.
     * @param protocol Protocol for RPC.
     * @param ticket UGI
     * @param rpcTimeout timeout
     * @param conf Configuration object
     * @return A ConnectionId instance
     * @throws IOException
     */
    static ConnectionId getConnectionId(InetSocketAddress addr,
        Class<?> protocol, UserGroupInformation ticket, int rpcTimeout,
        RetryPolicy connectionRetryPolicy, Configuration conf) throws IOException {


      //构建重试策略
      if (connectionRetryPolicy == null) {
        //设置最大重试次数 默认值: 10
        final int max = conf.getInt(
            CommonConfigurationKeysPublic.IPC_CLIENT_CONNECT_MAX_RETRIES_KEY,
            CommonConfigurationKeysPublic.IPC_CLIENT_CONNECT_MAX_RETRIES_DEFAULT);

        // 设置重试间隔: 1 秒
        final int retryInterval = conf.getInt(
            CommonConfigurationKeysPublic.IPC_CLIENT_CONNECT_RETRY_INTERVAL_KEY,
            CommonConfigurationKeysPublic
                .IPC_CLIENT_CONNECT_RETRY_INTERVAL_DEFAULT);

        //创建重试策略实例 RetryUpToMaximumCountWithFixedSleep
        //              重试10次, 每次间隔1秒
        connectionRetryPolicy = RetryPolicies.retryUpToMaximumCountWithFixedSleep(
            max, retryInterval, TimeUnit.MILLISECONDS);
      }

      //创建ConnectionId :
      //  这个类 持有 请求地址 和 用户的ticket
      //  client 连接 server 的唯一凭证 :  [remoteAddress, protocol, ticket]

      return new ConnectionId(addr, protocol, ticket, rpcTimeout,
          connectionRetryPolicy, conf);
    }
    

在getConnectionId这个方法里面会干两个事, 创一个重试策略 [RetryUpToMaximumCountWithFixedSleep]. 然后构建一个ConnectionId对象.

ConnectionId(InetSocketAddress address, Class<?> protocol, 
                 UserGroupInformation ticket, int rpcTimeout,
                 RetryPolicy connectionRetryPolicy, Configuration conf) {

      // 协议
      this.protocol = protocol;

      // 请求地址
      this.address = address;

      //用户 ticket
      this.ticket = ticket;

      //设置超时时间
      this.rpcTimeout = rpcTimeout;

      //设置重试策略 默认: 重试10次, 每次间隔1秒
      this.connectionRetryPolicy = connectionRetryPolicy;

      // 单位 10秒
      this.maxIdleTime = conf.getInt(
          CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_KEY,
          CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_DEFAULT);

      // sasl client最大重试次数 5 次
      this.maxRetriesOnSasl = conf.getInt(
          CommonConfigurationKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_ON_SASL_KEY,
          CommonConfigurationKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_ON_SASL_DEFAULT);

      //指示客户端将在套接字超时时进行重试的次数,以建立服务器连接。 默认值: 45
      this.maxRetriesOnSocketTimeouts = conf.getInt(
          CommonConfigurationKeysPublic.IPC_CLIENT_CONNECT_MAX_RETRIES_ON_SOCKET_TIMEOUTS_KEY,
          CommonConfigurationKeysPublic.IPC_CLIENT_CONNECT_MAX_RETRIES_ON_SOCKET_TIMEOUTS_DEFAULT);

      //使用TCP_NODELAY标志绕过Nagle的算法传输延迟。 默认值: true
      this.tcpNoDelay = conf.getBoolean(
          CommonConfigurationKeysPublic.IPC_CLIENT_TCPNODELAY_KEY,
          CommonConfigurationKeysPublic.IPC_CLIENT_TCPNODELAY_DEFAULT);

      // 从客户端启用低延迟连接 默认 false
      this.tcpLowLatency = conf.getBoolean(
          CommonConfigurationKeysPublic.IPC_CLIENT_LOW_LATENCY,
          CommonConfigurationKeysPublic.IPC_CLIENT_LOW_LATENCY_DEFAULT
          );

      // 启用从RPC客户端到服务器的ping操作 默认值: true
      this.doPing = conf.getBoolean(
          CommonConfigurationKeys.IPC_CLIENT_PING_KEY,
          CommonConfigurationKeys.IPC_CLIENT_PING_DEFAULT);

      // 设置ping 操作的间隔, 默认值 : 1分钟
      this.pingInterval = (doPing ? Client.getPingInterval(conf) : 0);
      this.conf = conf;
    }


 

回到之前的调用Invoker 另一个构造方法,但是入参会变.

    /**
     * This constructor takes a connectionId, instead of creating a new one.
     */
    private Invoker(Class<?> protocol, Client.ConnectionId connId,
        Configuration conf, SocketFactory factory) {
      this.remoteId = connId;
      this.client = CLIENTS.getClient(conf, factory, RpcWritable.Buffer.class);
      this.protocolName = RPC.getProtocolName(protocol);
      this.clientProtocolVersion = RPC
          .getProtocolVersion(protocol);
    }

这个是Invoker真正的构建方法,这里面会将刚刚构建好的ConnectionId 赋值给remoteId 字段.

并且创建一个Client 对象.

// 获取/创建  客户端
this.client = CLIENTS.getClient(conf, factory, RpcWritable.Buffer.class);

我们看下getClient 方法. 这里面会先尝试从缓存中获取client对象, 如果没有的话,在自己创建一个,并且加到缓存中.

为什么会放到缓存中呢?? 

当client和server再次通讯的时候,可以复用这个client . 

/**
   * 如果没有缓存的client存在的话
   * 根据用户提供的SocketFactory 构造 或者 缓存一个IPC 客户端
   *
   * Construct & cache an IPC client with the user-provided SocketFactory 
   * if no cached client exists.
   * 
   * @param conf Configuration
   * @param factory SocketFactory for client socket
   * @param valueClass Class of the expected response
   * @return an IPC client
   */
  public synchronized Client getClient(Configuration conf,
      SocketFactory factory, Class<? extends Writable> valueClass) {
    // Construct & cache client.
    //
    // The configuration is only used for timeout,
    // and Clients have connection pools.  So we can either
    // (a) lose some connection pooling and leak sockets, or
    // (b) use the same timeout for all configurations.
    //
    // Since the IPC is usually intended globally, notper-job, we choose (a).

    //从缓存中获取Client
    Client client = clients.get(factory);

    if (client == null) {
      //client在缓存中不存在, 创建一个.
      client = new Client(valueClass, conf, factory);
      //缓存创建的client
      clients.put(factory, client);
    } else {
      //client的引用计数+1
      client.incCount();
    }


    if (Client.LOG.isDebugEnabled()) {
      Client.LOG.debug("getting client out of cache: " + client);
    }
    // 返回client
    return client;
  }

 

到这里 Invoker 对象就创建完了. 

回到 ProtobufRpcEngine 的getProxy 方法 .

//然后调用Proxy.newProxylnstance()获取动态代理对象,并通过ProtocolProxy返回
    return new ProtocolProxy<T>(protocol, (T) Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker), false);

构建一个ProtocolProxy 对象返回

  /**
   * Constructor
   * 
   * @param protocol protocol class
   * @param proxy its proxy
   * @param supportServerMethodCheck If false proxy will never fetch server
   *        methods and isMethodSupported will always return true. If true,
   *        server methods will be fetched for the first call to 
   *        isMethodSupported. 
   */
  public ProtocolProxy(Class<T> protocol, T proxy,
      boolean supportServerMethodCheck) {
    this.protocol = protocol;
    this.proxy = proxy;
    this.supportServerMethodCheck = supportServerMethodCheck;
  }

 

然后我们看Client端的第四步 , 根据proto协议,构建一个请求对象.

这个没啥可说的.是proto自动生成的,我们只是创建了一下而已.

//4. 构建发送请求对象
 CustomProtos.GetMetaInfoRequestProto obj =  CustomProtos.GetMetaInfoRequestProto.newBuilder().setPath("/meta").build();

然后就是Client端的第5步了将请求对象传入, 获取响应信息

//5. 将请求对象传入, 获取响应信息
CustomProtos.GetMetaInfoResponseProto metaData = proxy.getMetaInfo(null, obj);

 

Client端的最后一步输出响应信息.

//6. 输出数据
System.out.println(metaData.getInfo());

------------------华丽的分割线-------------------------------------------------------------------

咦,到这里有点懵, 请求server端的代码呢??? 请求怎么发出去的??? 怎么拿到响应信息的呢????

嗯嗯,是动态代理. ProtobufRpcEngine 的getProxy 方法 .

return new ProtocolProxy<T>(protocol, (T) Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker), false);

主要是这个

(T) Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker)

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) {

  ........................

}

 

这个方法的作用就是创建一个代理类对象,

它接收三个参数,我们来看下几个参数的含义:

loader :       一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载


interfaces: 一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。


h:                一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。

 

我们直接看 ProtobufRpcEngine#Invoker中的 invoke方法

/**
     *
     * ProtobufRpcEngine.Invoker.invoker() 方法主要做了三件事情:
     *  1.构造请求头域,
     *    使用protobuf将请求头序列化,这个请求头域 记录了当前RPC调用是什么接口的什么方法上的调用;
     *  2.通过RPC.Client类发送请求头以 及序列化好的请求参数。
     *    请求参数是在ClientNamenodeProtocolPB调用时就已经序列化好的,
     *    调用Client.call()方法时,
     *    需要将请求头以及请求参数使用一个RpcRequestWrapper对象封装;
     *  3.获取响应信息,序列化响应信息并返回。
     *
     *
     * This is the client side invoker of RPC method. It only throws
     * ServiceException, since the invocation proxy expects only
     * ServiceException to be thrown by the method in case protobuf service.
     * 
     * ServiceException has the following causes:
     * <ol>
     * <li>Exceptions encountered on the client side in this method are 
     * set as cause in ServiceException as is.</li>
     * <li>Exceptions from the server are wrapped in RemoteException and are
     * set as cause in ServiceException</li>
     * </ol>
     * 
     * Note that the client calling protobuf RPC methods, must handle
     * ServiceException by getting the cause from the ServiceException. If the
     * cause is RemoteException, then unwrap it to get the exception thrown by
     * the server.
     */
    @Override
    public Message invoke(Object proxy, final Method method, Object[] args)
        throws ServiceException {
      long startTime = 0;
      if (LOG.isDebugEnabled()) {
        startTime = Time.now();
      }

      // pb接口的参数只有两个,即RpcController + Message
      if (args.length != 2) { // RpcController + Message
        throw new ServiceException(
            "Too many or few parameters for request. Method: ["
            + method.getName() + "]" + ", Expected: 2, Actual: "
            + args.length);
      }
      if (args[1] == null) {
        throw new ServiceException("null param while calling Method: ["
            + method.getName() + "]");
      }

      // if Tracing is on then start a new span for this rpc.
      // guard it in the if statement to make sure there isn't
      // any extra string manipulation.
      // todo 这个是啥
      Tracer tracer = Tracer.curThreadTracer();
      TraceScope traceScope = null;
      if (tracer != null) {
        traceScope = tracer.newScope(RpcClientUtil.methodToTraceString(method));
      }

      //构造请求头域,标明在什么接口上调用什么方法
      RequestHeaderProto rpcRequestHeader = constructRpcRequestHeader(method);
      
      if (LOG.isTraceEnabled()) {
        LOG.trace(Thread.currentThread().getId() + ": Call -> " +
            remoteId + ": " + method.getName() +
            " {" + TextFormat.shortDebugString((Message) args[1]) + "}");
      }


      //获取请求调用的参数,例如RenameRequestProto
      final Message theRequest = (Message) args[1];
      final RpcWritable.Buffer val;
      try {

        //调用RPC.Client发送请求
        val = (RpcWritable.Buffer) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
            new RpcProtobufRequest(rpcRequestHeader, theRequest), remoteId,
            fallbackToSimpleAuth, alignmentContext);

      } catch (Throwable e) {
        if (LOG.isTraceEnabled()) {
          LOG.trace(Thread.currentThread().getId() + ": Exception <- " +
              remoteId + ": " + method.getName() +
                " {" + e + "}");
        }
        if (traceScope != null) {
          traceScope.addTimelineAnnotation("Call got exception: " +
              e.toString());
        }
        throw new ServiceException(e);
      } finally {
        if (traceScope != null) traceScope.close();
      }

      if (LOG.isDebugEnabled()) {
        long callTime = Time.now() - startTime;
        LOG.debug("Call: " + method.getName() + " took " + callTime + "ms");
      }
      
      if (Client.isAsynchronousMode()) {
        final AsyncGet<RpcWritable.Buffer, IOException> arr
            = Client.getAsyncRpcResponse();
        final AsyncGet<Message, Exception> asyncGet
            = new AsyncGet<Message, Exception>() {
          @Override
          public Message get(long timeout, TimeUnit unit) throws Exception {
            return getReturnMessage(method, arr.get(timeout, unit));
          }

          @Override
          public boolean isDone() {
            return arr.isDone();
          }
        };
        ASYNC_RETURN_MESSAGE.set(asyncGet);
        return null;
      } else {
        return getReturnMessage(method, val);
      }
    }

 

艾玛,这个有点长啊.

挑几点重要的说.

  1. 构造请求头域,标明在什么接口上调用什么方法
  2. 获取请求调用的参数
  3. 调用RPC.Client发送请求
  4. 获取响应信息

下面分别来说:

1.构造请求头域,标明在什么接口上调用什么方法

//构造请求头域,标明在什么接口上调用什么方法
RequestHeaderProto rpcRequestHeader = constructRpcRequestHeader(method);
      

这里很简单,就是根据协议定义,将  协议名称, 调用的方法名称, 版本号, 三个值传入,构建一个消息头.

    private RequestHeaderProto constructRpcRequestHeader(Method method) {
      RequestHeaderProto.Builder builder = RequestHeaderProto
          .newBuilder();
      builder.setMethodName(method.getName());
      builder.setDeclaringClassProtocolName(protocolName);
      builder.setClientProtocolVersion(clientProtocolVersion);
      return builder.build();
    }

 

 

2.获取请求调用的参数

 

获取请求调用的参数,这个是才client端代码就创建好的,通过参数传进来的.

      // 获取请求调用的参数,这个是才client端代码就创建好的,通过参数传进来的.
      // 例如 GetMetaInfoRequestProto
      final Message theRequest = (Message) args[1];

 

3.调用RPC.Client发送请求

这个是核心,毕竟是发送请求,我们来重点关注一下.

         //调用RPC.Client发送请求
        val = (RpcWritable.Buffer) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
            new RpcProtobufRequest(rpcRequestHeader, theRequest), remoteId,
            fallbackToSimpleAuth, alignmentContext);

Client这个我就不说了,ProtobufRpcEngine#Invoker构造方法里面创建的.

   /**
     * This constructor takes a connectionId, instead of creating a new one.
     */
    private Invoker(Class<?> protocol, Client.ConnectionId connId,
        Configuration conf, SocketFactory factory) {

      // 设置ConnectionId , ConnectionId里面保存着Client连接Server的信息
      this.remoteId = connId;

      // 获取/创建  客户端
      this.client = CLIENTS.getClient(conf, factory, RpcWritable.Buffer.class);

      // 获取协议的名称
      this.protocolName = RPC.getProtocolName(protocol);

      // 获取协议的版本 version
      this.clientProtocolVersion = RPC
          .getProtocolVersion(protocol);
    }

我们直接看Client#call 方法.

方法有点长. 摘取主要的说.

3.1.创建 Call 对象

3.2.获取&建立连接

3.3.发送RPC请求

3.4.获取响应

/**
   *
   * Make a call, passing <code>rpcRequest</code>, to the IPC server defined by
   * <code>remoteId</code>, returning the rpc response.
   *
   * @param rpcKind
   * @param rpcRequest -  contains serialized method and method parameters
   * @param remoteId - the target rpc server
   * @param serviceClass - service class for RPC
   * @param fallbackToSimpleAuth - set to true or false during this method to
   *   indicate if a secure client falls back to simple auth
   * @param alignmentContext - state alignment context
   * @return the rpc response
   * Throws exceptions if there are network problems or if the remote code
   * threw an exception.
   */
  Writable call(RPC.RpcKind rpcKind, Writable rpcRequest,
      ConnectionId remoteId, int serviceClass,
      AtomicBoolean fallbackToSimpleAuth, AlignmentContext alignmentContext)
      throws IOException {


    /**
     * 创建 Call 对象
     * Client.call()方法将RPC请求封装成一个Call对象,
     * Call对象中保存了RPC调用的完 成标志、返回值信息以及异常信息;
     * 随后,Client.cal()方法会创建一个 Connection对象,
     * Connection对象用于管理Client与Server的Socket连接。
     */
    final Call call = createCall(rpcKind, rpcRequest);


    call.setAlignmentContext(alignmentContext);


    //获取&建立连接
    //用ConnectionId作为key,
    // 将新建的Connection对象放入Client.connections字段中保 存
    // (
    // 对于Connection对象,
    // 由于涉及了与Server建立Socket连接,会比较耗费资 源,
    // 所以Client类使用一个HashTable对象connections保存那些没有过期的 Connection,
    // 如果可以复用,则复用这些Connection对象
    // );
    // 以callId作为key,将 构造的Call对象放入Connection.calls字段中保存。

    final Connection connection = getConnection(remoteId, call, serviceClass,
        fallbackToSimpleAuth);

    try {
      //检测是否是异步请求.
      checkAsyncCall();
      try {
        //发送RPC请求
        connection.sendRpcRequest(call);                 // send the rpc request


      } catch (RejectedExecutionException e) {
        throw new IOException("connection has been closed", e);
      } catch (InterruptedException ie) {
        Thread.currentThread().interrupt();
        IOException ioe = new InterruptedIOException(
            "Interrupted waiting to send RPC request to server");
        ioe.initCause(ie);
        throw ioe;
      }
    } catch(Exception e) {
      if (isAsynchronousMode()) {
        releaseAsyncCall();
      }
      throw e;
    }

    if (isAsynchronousMode()) {
      final AsyncGet<Writable, IOException> asyncGet
          = new AsyncGet<Writable, IOException>() {
        @Override
        public Writable get(long timeout, TimeUnit unit)
            throws IOException, TimeoutException{
          boolean done = true;
          try {
            final Writable w = getRpcResponse(call, connection, timeout, unit);
            if (w == null) {
              done = false;
              throw new TimeoutException(call + " timed out "
                  + timeout + " " + unit);
            }
            return w;
          } finally {
            if (done) {
              releaseAsyncCall();
            }
          }
        }

        @Override
        public boolean isDone() {
          synchronized (call) {
            return call.done;
          }
        }
      };

      ASYNC_RPC_RESPONSE.set(asyncGet);
      return null;
    } else {

      //服务器成功发回响应信息,返回RPC响应
      return getRpcResponse(call, connection, -1, null);
    }
  }

3.1.创建 Call 对象

    /**
     * 创建 Call 对象
     * Client.call()方法将RPC请求封装成一个Call对象,
     * Call对象中保存了RPC调用的完 成标志、返回值信息以及异常信息;
     * 随后,Client.cal()方法会创建一个 Connection对象,
     * Connection对象用于管理Client与Server的Socket连接。
     */
    final Call call = createCall(rpcKind, rpcRequest);
  /**
   *
   * @param rpcKind  rpcKind参数用于描述RPC请求的序列化工具类型
   * @param rpcRequest rpcRequest参数则用于记录序列化后的RPC请求
   * @return
   */
  Call createCall(RPC.RpcKind rpcKind, Writable rpcRequest) {
    return new Call(rpcKind, rpcRequest);
  }

这里就是构造方法, 生成一个唯一的callId ,进行初始化 .

private Call(RPC.RpcKind rpcKind, Writable param) {
      this.rpcKind = rpcKind;
      this.rpcRequest = param;

      //获取 callId
      final Integer id = callId.get();
      if (id == null) {
        // AtomicInteger  callIdCounter  自增
        this.id = nextCallId();
      } else {
        callId.set(null);
        this.id = id;
      }
      
      final Integer rc = retryCount.get();
      if (rc == null) {
        this.retry = 0;
      } else {
        this.retry = rc;
      }
      // 设置异常处理类
      this.externalHandler = EXTERNAL_CALL_HANDLER.get();
    }

 

3.2.获取&建立连接


用ConnectionId作为key,
 将新建的Connection对象放入Client.connections字段中保 存
 (
 对于Connection对象,
 由于涉及了与Server建立Socket连接,会比较耗费资 源,
 所以Client类使用一个ConcurrentMap对象connections保存那些没有过期的 Connection,
 如果可以复用,则复用这些Connection对象
 );
 以callId作为key,将 构造的Call对象放入Connection.calls字段中保存。

 final Connection connection = getConnection(remoteId, call, serviceClass,
        fallbackToSimpleAuth);

 

/** Get a connection from the pool, or create a new one and add it to the
   * pool.  Connections to a given ConnectionId are reused.
   * 获取连接
   *
   * */
  private Connection getConnection(ConnectionId remoteId,
      Call call, int serviceClass, AtomicBoolean fallbackToSimpleAuth)
      throws IOException {
    if (!running.get()) {
      // the client is stopped
      throw new IOException("The client is stopped");
    }
    Connection connection;
    /* we could avoid this allocation for each RPC by having a  
     * connectionsId object and with set() method. We need to manage the
     * refs for keys in HashMap properly. For now its ok.
     */
    while (true) {
      // These lines below can be shorten with computeIfAbsent in Java8
      //首先尝试从Client.connections队列中获取Connection对象
      connection = connections.get(remoteId);
      if (connection == null) {

        //如果connections队列中没有保存,则构造新的对象
        connection = new Connection(remoteId, serviceClass);

        // putIfAbsent 如果对应的 key 已经有值了,
        // 则忽略本次操作,直接返回旧值

        Connection existing = connections.putIfAbsent(remoteId, connection);

        if (existing != null) {
          connection = existing;
        }
      }

      //将待发送请求对应的Call对象放入Connection.calls队列
      if (connection.addCall(call)) {
        break;
      } else {
        // This connection is closed, should be removed. But other thread could
        // have already known this closedConnection, and replace it with a new
        // connection. So we should call conditional remove to make sure we only
        // remove this closedConnection.

        connections.remove(remoteId, connection);
      }
    }

    // If the server happens to be slow, the method below will take longer to
    // establish a connection.

    //调用setupIOstreams()方法,初始化Connection对象并获取IO流
    connection.setupIOstreams(fallbackToSimpleAuth);
    return connection;
  }

里面创建connection就不细说了, 其实就是创建connection对象,然后做一下请求地址和请求参数的设置. 并没有和server端进行请求.

//如果connections队列中没有保存,则构造新的对象
 connection = new Connection(remoteId, serviceClass);

 

connection.setupIOstreams 这个才是建立连接的关键方法.

    //调用setupIOstreams()方法,初始化Connection对象并获取IO流
    connection.setupIOstreams(fallbackToSimpleAuth);

 

    /**
     *
     *
     * Connect to the server and set up the I/O streams. It then sends
     * a header to the server and starts
     * the connection thread that waits for responses.
     *
     * Client.call()方法调用 Connection.setupIOstreams() 方法建立与Server的Socket连接。
     * setupIOstreams() 方法还会启动Connection线程,
     * Connection线程会监听Socket并读取Server发回的响应信息。
     *
     */
    private synchronized void setupIOstreams( AtomicBoolean fallbackToSimpleAuth) {
      if (socket != null || shouldCloseConnection.get()) {
        return;
      }

      UserGroupInformation ticket = remoteId.getTicket();

      if (ticket != null) {
        final UserGroupInformation realUser = ticket.getRealUser();
        if (realUser != null) {
          ticket = realUser;
        }
      }

      try {
        connectingThread.set(Thread.currentThread());
        if (LOG.isDebugEnabled()) {
          LOG.debug("Connecting to "+server);
        }
        Span span = Tracer.getCurrentSpan();
        if (span != null) {
          span.addTimelineAnnotation("IPC client connecting to " + server);
        }
        short numRetries = 0;
        Random rand = null;
        while (true) {



          //建立到Server的Socket连接,
          // 并且在这个Socket连接上 获得InputStream和OutputStream对象。
          setupConnection(ticket);


          ipcStreams = new IpcStreams(socket, maxResponseLength);

          // 调用writeConnectionHeader()方法在连接建立时发送连接头域。
          writeConnectionHeader(ipcStreams);

          if (authProtocol == AuthProtocol.SASL) {
            try {
              authMethod = ticket
                  .doAs(new PrivilegedExceptionAction<AuthMethod>() {
                    @Override
                    public AuthMethod run()
                        throws IOException, InterruptedException {
                      return setupSaslConnection(ipcStreams);
                    }
                  });
            } catch (IOException ex) {
              if (saslRpcClient == null) {
                // whatever happened -it can't be handled, so rethrow
                throw ex;
              }
              // otherwise, assume a connection problem
              authMethod = saslRpcClient.getAuthMethod();
              if (rand == null) {
                rand = new Random();
              }
              handleSaslConnectionFailure(numRetries++, maxRetriesOnSasl, ex,
                  rand, ticket);
              continue;
            }
            if (authMethod != AuthMethod.SIMPLE) {
              // Sasl connect is successful. Let's set up Sasl i/o streams.
              ipcStreams.setSaslClient(saslRpcClient);
              // for testing
              remoteId.saslQop =
                  (String)saslRpcClient.getNegotiatedProperty(Sasl.QOP);
              LOG.debug("Negotiated QOP is :" + remoteId.saslQop);
              if (fallbackToSimpleAuth != null) {
                fallbackToSimpleAuth.set(false);
              }
            } else if (UserGroupInformation.isSecurityEnabled()) {
              if (!fallbackAllowed) {
                throw new IOException("Server asks us to fall back to SIMPLE " +
                    "auth, but this client is configured to only allow secure " +
                    "connections.");
              }
              if (fallbackToSimpleAuth != null) {
                fallbackToSimpleAuth.set(true);
              }
            }
          }

          if (doPing) {
            ipcStreams.setInputStream(new PingInputStream(ipcStreams.in));
          }

          //调用writeConnectionContext()方法写入连接上下文。
          writeConnectionContext(remoteId, authMethod);

          // update last activity time
          //调用touch()方法更新上次活跃时间。
          touch();

          span = Tracer.getCurrentSpan();
          if (span != null) {
            span.addTimelineAnnotation("IPC client connected to " + server);
          }


          // 开启接收线程
          // 调用start()方法启动Connection线程监听并接收Server发回的响应信息。
          // start the receiver thread after the socket connection has been set up
          start();


          return;
        }
      } catch (Throwable t) {
        if (t instanceof IOException) {
          markClosed((IOException)t);
        } else {
          markClosed(new IOException("Couldn't set up IO streams: " + t, t));
        }
        close();
      } finally {
        connectingThread.set(null);
      }
    }

在这个方法中通过setupConnection(ticket); 方法与server端建立连接.

          //建立到Server的Socket连接,
          // 并且在这个Socket连接上 获得InputStream和OutputStream对象。
          setupConnection(ticket);

 

调用writeConnectionHeader()方法在连接建立时发送连接头信息。

writeConnectionHeader(ipcStreams);
    /* Write the connection context header for each connection
     * Out is not synchronized because only the first thread does this.
     */
    private void writeConnectionContext(ConnectionId remoteId,
                                        AuthMethod authMethod)
                                            throws IOException {
      // Write out the ConnectionHeader
      IpcConnectionContextProto message = ProtoUtil.makeIpcConnectionContext(
          RPC.getProtocolName(remoteId.getProtocol()),
          remoteId.getTicket(),
          authMethod);
      RpcRequestHeaderProto connectionContextHeader = ProtoUtil
          .makeRpcRequestHeader(RpcKind.RPC_PROTOCOL_BUFFER,
              OperationProto.RPC_FINAL_PACKET, CONNECTION_CONTEXT_CALL_ID,
              RpcConstants.INVALID_RETRY_COUNT, clientId);
      // do not flush.  the context and first ipc call request must be sent
      // together to avoid possibility of broken pipes upon authz failure.
      // see writeConnectionHeader
      final ResponseBuffer buf = new ResponseBuffer();
      connectionContextHeader.writeDelimitedTo(buf);
      message.writeDelimitedTo(buf);


      synchronized (ipcStreams.out) {
        ipcStreams.sendRequest(buf.toByteArray());
      }

    }

开启接收线程 调用start()方法启动Connection线程监听并接收Server发回的响应信息。

          // 开启接收线程
          // 调用start()方法启动Connection线程监听并接收Server发回的响应信息。
          // start the receiver thread after the socket connection has been set up
          start();

 

在这里会调用Connection里面的run()方法. 接收并处理返回消息.

    @Override
    public void run() {
      if (LOG.isDebugEnabled())
        LOG.debug(getName() + ": starting, having connections " 
            + connections.size());

      try {
        while (waitForWork()) {//wait here for work - read or close connection

          //接收到返回信息
          receiveRpcResponse();

        }
      } catch (Throwable t) {
        // This truly is unexpected, since we catch IOException in receiveResponse
        // -- this is only to be really sure that we don't leave a client hanging
        // forever.
        LOG.warn("Unexpected error reading responses on connection " + this, t);
        markClosed(new IOException("Error reading responses", t));
      }
      
      close();
      
      if (LOG.isDebugEnabled())
        LOG.debug(getName() + ": stopped, remaining connections "
            + connections.size());
    }
/**
       接收到返回信息
       Receive a response.
     * Because only one receiver, so no synchronization on in.
     * receiveRpcResponse()方法接收RPC响应。
     * receiveRpcResponse()方法 会从输入流中读取序列化对象RpcResponseHeaderProto,
     * 然后根据RpcResponseHeaderProto 中记录的callid字段获取对应的Call的对象。
     *
     * 接下来receiveRpcResponse()方法会从输入流中 读取响应消息,
     * 然后调用Call.setRpcResponse()将响应消息保存在Call对象中。
     * 如果服务器 在处理RPC请求时抛出异常,
     * 则receiveRpcResponse()会从输入流中读取异常信息,并构造 异常对象,
     * 然后调用Call.setException()将异常保存在Call对象中。
     *
     */
    private void receiveRpcResponse() {
      if (shouldCloseConnection.get()) {
        return;
      }
      //更新请求时间的
      touch();
      
      try {
        //通过ipcStreams 获取响应对象
        ByteBuffer bb = ipcStreams.readResponse();

        //将响应对象,采用RpcWritable 进行包装.
        RpcWritable.Buffer packet = RpcWritable.Buffer.wrap(bb);

        // 获取响应的响应头.
        RpcResponseHeaderProto header = packet.getValue(RpcResponseHeaderProto.getDefaultInstance());

        // 检测头信息.
        checkResponse(header);


        // 获取call唯一标识callId
        int callId = header.getCallId();

        if (LOG.isDebugEnabled())
          LOG.debug(getName() + " got value #" + callId);

        // 获取头信息里面的响应状态.
        RpcStatusProto status = header.getStatus();

        //如果调用成功,则读取响应消息,在call实例中设置
        if (status == RpcStatusProto.SUCCESS) {

          //构建实例
          Writable value = packet.newInstance(valueClass, conf);

          //将Call 从 正在执行的calls缓存队列中移除.
          final Call call = calls.remove(callId);

          //将Call 设置请求信息
          call.setRpcResponse(value);

          
          if (call.alignmentContext != null) {
            call.alignmentContext.receiveResponseState(header);
          }
        }
        // verify that packet length was correct
        if (packet.remaining() > 0) {
          throw new RpcClientException("RPC response length mismatch");
        }

        //RPC调用失败
        if (status != RpcStatusProto.SUCCESS) { // Rpc Request failed

          //取出响应中的异常消息
          final String exceptionClassName = header.hasExceptionClassName() ?
                header.getExceptionClassName() : 
                  "ServerDidNotSetExceptionClassName";

          final String errorMsg = header.hasErrorMsg() ? 
                header.getErrorMsg() : "ServerDidNotSetErrorMsg" ;
          final RpcErrorCodeProto erCode = 
                    (header.hasErrorDetail() ? header.getErrorDetail() : null);
          if (erCode == null) {
             LOG.warn("Detailed error code not set by server on rpc error");
          }

          RemoteException re = new RemoteException(exceptionClassName, errorMsg, erCode);

          //在Call对象中设置异常
          if (status == RpcStatusProto.ERROR) {
            final Call call = calls.remove(callId);
            call.setException(re);
          } else if (status == RpcStatusProto.FATAL) {
            // Close the connection
            markClosed(re);
          }
        }
      } catch (IOException e) {
        markClosed(e);
      }
    }

 

 

3.3.发送RPC请求

先构造RPC请求头. 将请求头和RPC请求 ,在 另外一个线程池  sendParamsExecutor的run方法中使用ipcStreams.sendRequest(buf.toByteArray()); 发送出去.    里面写的太麻烦了, 自己看吧.

 

         //发送RPC请求
        connection.sendRpcRequest(call);                 // send the rpc request

 

/**
     *
     * 发送RPC请求到Server。
     *
     * Initiates a rpc call by sending the rpc request to the remote server.
     * Note: this is not called from the Connection thread, but by other
     * threads.
     * @param call - the rpc request
     *
     * RPC发送请求线程会调用Connection.sendRpcRequest()方法发送RPC请求到Server,
     * 这 里要特别注意,这个方法不是由Connection线程调用的,
     *             而是由发起RPC请求的线程调用 的。
     *
     */
    public void sendRpcRequest(final Call call)
        throws InterruptedException, IOException {
      if (shouldCloseConnection.get()) {
        return;
      }

      // Serialize the call to be sent. This is done from the actual
      // caller thread, rather than the sendParamsExecutor thread,
      
      // so that if the serialization throws an error, it is reported
      // properly. This also parallelizes the serialization.
      //
      // Format of a call on the wire:
      // 0) Length of rest below (1 + 2)
      // 1) RpcRequestHeader  - is serialized Delimited hence contains length
      // 2) RpcRequest
      //
      // Items '1' and '2' are prepared here.

      //先构造RPC请求头
      RpcRequestHeaderProto header = ProtoUtil.makeRpcRequestHeader(
          call.rpcKind, OperationProto.RPC_FINAL_PACKET, call.id, call.retry,
          clientId, call.alignmentContext);


      final ResponseBuffer buf = new ResponseBuffer();

      //将RPC请求头写入  输出流 ResponseBuffer
      header.writeDelimitedTo(buf);


      //将RPC请求(包括请求元数据和请求参数)封装成ProtobufWrapper  ==> 写入输出流
      RpcWritable.wrap(call.rpcRequest).writeTo(buf);


      //这里使用 线程池 将请求发送出去,
      // 请求包括三个部分:
      // 1  长度;
      // 2  RPC请求头;
      // 3  RPC请求(包括 请求元数据以及请求参数)
      synchronized (sendRpcRequestLock) {
        Future<?> senderFuture = sendParamsExecutor.submit(new Runnable() {
          @Override
          public void run() {
            try {
              synchronized (ipcStreams.out) {
                if (shouldCloseConnection.get()) {
                  return;
                }
                if (LOG.isDebugEnabled()) {
                  LOG.debug(getName() + " sending #" + call.id
                      + " " + call.rpcRequest);
                }
                // RpcRequestHeader + RpcRequest
                ipcStreams.sendRequest(buf.toByteArray());
                ipcStreams.flush();
              }
            } catch (IOException e) {
              // exception at this point would leave the connection in an
              // unrecoverable state (eg half a call left on the wire).
              // So, close the connection, killing any outstanding calls
              //如果发生发送异常,则直接关闭连接
              markClosed(e);
            } finally {
              //the buffer is just an in-memory buffer, but it is still polite to
              // close early
              //之前申请的buffer给关闭了,比较优雅
              IOUtils.closeStream(buf);
            }
          }
        });
      
        try {
          senderFuture.get();
        } catch (ExecutionException e) {
          Throwable cause = e.getCause();
          
          // cause should only be a RuntimeException as the Runnable above
          // catches IOException
          if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
          } else {
            throw new RuntimeException("unexpected checked exception", cause);
          }
        }
      }
    }

 

 

 

 

 

3.4.获取响应

 

 

//服务器成功发回响应信息,返回RPC响应
return getRpcResponse(call, connection, -1, null);

在这里,会不断轮训call的状态, 如果状态为done则代表数据已经处理完,并且已经获取到响应信息.

响应信息由connection中的run方法进行处理.

/** @return the rpc response or, in case of timeout, null. */
  private Writable getRpcResponse(final Call call, final Connection connection,
      final long timeout, final TimeUnit unit) throws IOException {
    synchronized (call) {
      while (!call.done) {
        try {

          //等待RPC响应
          AsyncGet.Util.wait(call, timeout, unit);
          if (timeout >= 0 && !call.done) {
            return null;
          }
        } catch (InterruptedException ie) {
          Thread.currentThread().interrupt();
          throw new InterruptedIOException("Call interrupted");
        }
      }

      if (call.error != null) {
        if (call.error instanceof RemoteException) {
          call.error.fillInStackTrace();
          throw call.error;
        } else { // local exception
          InetSocketAddress address = connection.getRemoteAddress();
          throw NetUtils.wrapException(address.getHostName(),
                  address.getPort(),
                  NetUtils.getHostname(),
                  0,
                  call.error);
        }
      } else {
        return call.getRpcResponse();
      }
    }

 

 

 

 

 

 

 

 

 

 

 

4. 处理Client 端的响应信息

Client 获取响应信息, 不过是同步还是异步获取响应信息,都会调用这个方法:  getReturnMessage(method, val);

getReturnMessage(method, val);

 

    private Message getReturnMessage(final Method method,
        final RpcWritable.Buffer buf) throws ServiceException {
      Message prototype = null;
      try {
        //获取返回参数
        prototype = getReturnProtoType(method);
      } catch (Exception e) {
        throw new ServiceException(e);
      }
      Message returnMessage;
      try {
        // 序列化响应信息
        returnMessage = buf.getValue(prototype.getDefaultInstanceForType());

        if (LOG.isTraceEnabled()) {
          LOG.trace(Thread.currentThread().getId() + ": Response <- " +
              remoteId + ": " + method.getName() +
                " {" + TextFormat.shortDebugString(returnMessage) + "}");
        }

      } catch (Throwable e) {
        throw new ServiceException(e);
      }

      //返回结果
      return returnMessage;
    }

 

最后我们看client端最后的步骤. 输出返回的信息.

 

 

 


 

 

 

 

 

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值