Hadoop源码分析(14)

Hadoop源码分析(14)

1、 RPC解析

 在文档(13)中介绍HDFS的RPC,并演示了如何直接使用HDFS的RPC。 在HDFS的源码中使用RPC的方式与文档(13)中使用的方式相同。对于RPC的客户端 来说都是需要获取一个代理对象,然后利用代理对象将数据发送给Server端。

 在文档(12)中分析到了namenode在启动的时候,需要加载元数据。 而元数据分为两类FSImage和EditLog。对于配置了高可用的namenode来说 editLog存储在配置的journalnode中。而namenode与journalnode通常不 在同一台机器中,所以两者需要通过rpc进行通信。

 namenode会通过一个selectInputStreams方法来读取journalnode 中editLog,这个方法会先调用getEditLogManifest方法获取journalnode中 editLog列表,然后根据这里数据来生成editlog的输入流。而这里的 getEditLogManifest方法会调用IPCLoggerChannel对象的 getEditLogManifest方法,这个方法会创建一个匿名线程类提交到线程池中。 而这个匿名类的call方法便是RPC的客户端。其内容如下:

 public RemoteEditLogManifest call() throws IOException {
        GetEditLogManifestResponseProto ret = getProxy().getEditLogManifest(
            journalId, fromTxnId, inProgressOk);
        // Update the http port, since we need this to build URLs to any of the
        // returned logs.
        constructHttpServerURI(ret);
        return PBHelper.convert(ret.getManifest());
      }

 重点在第2行,这里首先通过getProxy方法获取代理类,然后执行代理类 的getEditLogManifest方法。 getProxy方法的内容如下:

  protected QJournalProtocol getProxy() throws IOException {
    if (proxy != null) return proxy;
    proxy = createProxy();
    return proxy;
  }

 这个方法也很简单,就是当proxy 不为空的时候就直接返回,若为空 则调用createProxy方法创建proxy。创建proxy的方法内容如下:

protected QJournalProtocol createProxy() throws IOException {
    final Configuration confCopy = new Configuration(conf);

    // Need to set NODELAY or else batches larger than MTU can trigger 
    // 40ms nagling delays.
    confCopy.setBoolean(
        CommonConfigurationKeysPublic.IPC_CLIENT_TCPNODELAY_KEY,
        true);

    RPC.setProtocolEngine(confCopy,
        QJournalProtocolPB.class, ProtobufRpcEngine.class);
    return SecurityUtil.doAsLoginUser(
        new PrivilegedExceptionAction<QJournalProtocol>() {
          @Override
          public QJournalProtocol run() throws IOException {
            RPC.setProtocolEngine(confCopy,
                QJournalProtocolPB.class, ProtobufRpcEngine.class);
            QJournalProtocolPB pbproxy = RPC.getProxy(
                QJournalProtocolPB.class,
                RPC.getProtocolVersion(QJournalProtocolPB.class),
                addr, confCopy);
            return new QJournalProtocolTranslatorPB(pbproxy);
          }
        });
  }

  这段代码的重点在第15行的run方法中,这里会调用RPC的getProxy方 法来获取代理对象。这个方法与文档(13)中的示例是同一个方法。其内容如下:

   public static <T> T getProxy(Class<T> protocol,
                                 long clientVersion,
                                 InetSocketAddress addr, Configuration conf)
     throws IOException {

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

  这里实际就两个方法:getProtocolProxy和getProxy。 其中getProxy方法很简单,会直接返回该对象存储的proxy对象。 而getProtocolProxy才是真正创建代理对象的方法。 getProtocolProxy方法有多个重载方法,最终调用重载方法如下:

 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);
  }

  这里依然是只有两个方法:getProtocolEngine和getProxy。 getProtocolEngine方法用于获取engine类,这个类在上文的createProxy 方法中有提到。在该方法的第10行调用了RPC的setProtocolEngine方法将 engine类存储到配置文件中,setProtocolEngine方法内容如下:

  public static void setProtocolEngine(Configuration conf,
                                Class<?> protocol, Class<?> engine) {
    conf.setClass(ENGINE_PROP+"."+protocol.getName(), engine, RpcEngine.class);
  }

  这里的engine类是用来指定如何创建的代理对象的类,在默认的情况下 会是WritableRpcEngine类,这里设置的是ProtobufRpcEngine类。

  然后再来看获取engine类的getProtocolEngine方法,其内容如下:

static synchronized RpcEngine getProtocolEngine(Class<?> protocol,
      Configuration conf) {
    RpcEngine engine = PROTOCOL_ENGINES.get(protocol);
    if (engine == null) {
      Class<?> impl = conf.getClass(ENGINE_PROP+"."+protocol.getName(),
                                    WritableRpcEngine.class);
      engine = (RpcEngine)ReflectionUtils.newInstance(impl, conf);
      PROTOCOL_ENGINES.put(protocol, engine);
    }
    return engine;
  }

  这里也很简单,首先是第3行直接从PROTOCOL_ENGINES读取engine 类,若为空则再从配置文件中读取engine类,然后初始化并将读取到的类存储到 PROTOCOL_ENGINES中。最后再返回读取到的engine类的对象。

  从上面的分析可以得知这里返回的是ProtobufRpcEngine类的 对象,然后再看这个类的getProxy方法。其内容如下:

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

    final Invoker invoker = new Invoker(protocol, addr, ticket, conf, factory,
        rpcTimeout, connectionRetryPolicy, fallbackToSimpleAuth);
    return new ProtocolProxy<T>(protocol, (T) Proxy.newProxyInstance(
        protocol.getClassLoader(), new Class[]{protocol}, invoker), false);
  }

  这里首先是第6行,这里创建了一个invoker对象。这个对象很重要, 它是实际发送方法的实现类。然后第8行返回了一个新创建的ProtocolProxy对象, 这个对象创建的时候传入了三个参数,其中最重要的是第二个参数,它代表的是这段 代码需要创建的代理对象。

  这里创建代理对象是使用了Proxy类的newProxyInstance方法, 这是java反射中的类,常用来做动态代理。在java的api可以查到该方法的描述如下:

proxy类的API

  简单来说就是这个方法会自动生成一个代理类,这个代理类会实现传入 的接口(第二个参数),并返回一个该代理类的对象。生成的代理类的方法会调用传入 的实现了InvocationHandler接口的对象(第三个参数)的invoke方法。

  上文中传入的实现了InvocationHandler接口的对象是invoker, 其初始化方法如下:

 private Invoker(Class<?> protocol, InetSocketAddress addr,
        UserGroupInformation ticket, Configuration conf, SocketFactory factory,
        int rpcTimeout, RetryPolicy connectionRetryPolicy,
        AtomicBoolean fallbackToSimpleAuth) throws IOException {
      this(protocol, Client.ConnectionId.getConnectionId(
          addr, protocol, ticket, rpcTimeout, connectionRetryPolicy, conf),
          conf, factory);
      this.fallbackToSimpleAuth = fallbackToSimpleAuth;
    }
    private Invoker(Class<?> protocol, Client.ConnectionId connId,
        Configuration conf, SocketFactory factory) {
      this.remoteId = connId;
      this.client = CLIENTS.getClient(conf, factory, RpcResponseWrapper.class);
      this.protocolName = RPC.getProtocolName(protocol);
      this.clientProtocolVersion = RPC
          .getProtocolVersion(protocol);
    }

  其中第14行的client 参数比较重要,其通过CLIENTS的getClient 方法获取的。CLIENTS是invoker中的一个常量。getClient方法内容如下:

 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, not
    // per-job, we choose (a).
    Client client = clients.get(factory);
    if (client == null) {
      client = new Client(valueClass, conf, factory);
      clients.put(factory, client);
    } else {
      client.incCount();
    }
    if (Client.LOG.isDebugEnabled()) {
      Client.LOG.debug("getting client out of cache: " + client);
    }
    return client;
  }

  这里其实也很简单,就是先从自身存储的clients中获取。如果没有 则新建一个,并存储到clients中,最后返回获取到的对象。

  最后我们再看代理对象实际会调用的invoke方法,其内容如下:

 public Object invoke(Object proxy, Method method, Object[] args)
        throws ServiceException {
      long startTime = 0;
      if (LOG.isDebugEnabled()) {
        startTime = Time.now();
      }

      if (args.length != 2) { // RpcController + Message
        throw new ServiceException("Too many 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() + "]");
      }

      TraceScope traceScope = null;
      // 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.
      if (Trace.isTracing()) {
        traceScope = Trace.startSpan(RpcClientUtil.methodToTraceString(method));
      }

      RequestHeaderProto rpcRequestHeader = constructRpcRequestHeader(method);

      if (LOG.isTraceEnabled()) {
        LOG.trace(Thread.currentThread().getId() + ": Call -> " +
            remoteId + ": " + method.getName() +
            " {" + TextFormat.shortDebugString((Message) args[1]) + "}");
      }


      Message theRequest = (Message) args[1];
      final RpcResponseWrapper val;
      try {
        val = (RpcResponseWrapper) client.call(RPC.RpcKind.RPC_PROTOCOL_BUFFER,
            new RpcRequestWrapper(rpcRequestHeader, theRequest), remoteId,
            fallbackToSimpleAuth);

      } catch (Throwable e) {
        if (LOG.isTraceEnabled()) {
          LOG.trace(Thread.currentThread().getId() + ": Exception <- " +
              remoteId + ": " + method.getName() +
                " {" + e + "}");
        }
        if (Trace.isTracing()) {
          traceScope.getSpan().addTimelineAnnotation(
              "Call got exception: " + e.getMessage());
        }
        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");
      }

      Message prototype = null;
      try {
        prototype = getReturnProtoType(method);
      } catch (Exception e) {
        throw new ServiceException(e);
      }
      Message returnMessage;
      try {
        returnMessage = prototype.newBuilderForType()
            .mergeFrom(val.theResponseRead).build();

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

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

  首先是第3行到第24行,这里主要是一些参数检查。然后是第26行 处理传入method。然后第28行到第32行是在处理日志。然后是第35行处理传入 的参数。然后是第38行调用client的call方法访问server端,并等待 server端的返回。然后是第64行获取方法的返回值类型,然后是第70行处理 server端的返回值。最后是第82行返回方法的结果。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值