Hadoop源码分析(16)

1、 RPC解析

 在文档(15)中解析了实际进行远程调用client类的call方法。在该文档中提到的其本质是使用java的NIO与远端的服务器进行连接。并通过channel将需要执行的方法的数据发送过去,然后等待远端将执行结果返回给客户端。

  hdfs的组件中很多都有RPC的server端,之前文档分析到namenode启动的时候会从journalnode中读取其存储的editlog,因此需要使用RPC获取其存储的editlog列表。所以这里按照分析的思路,以journalnode的RPC服务端为例。

  这里先以journalnode的启动为入口,分析其如何启动RPC服务的。

  在文档(3)中提到了在文件hdfs中会有hdfs所有组件所用的类。其中启动journalnode所用的类为:org.apache.hadoop.hdfs.qjournal.server.JournalNode。这个类的main方法如下:

public static void main(String[] args) throws Exception {
    StringUtils.startupShutdownMessage(JournalNode.class, args, LOG);
    try {
      System.exit(ToolRunner.run(new JournalNode(), args));
    } catch (Throwable e) {
      LOG.error("Failed to start journalnode.", e);
      terminate(-1, e);
    }
  }

  重点在第4行,这行的重点是ToolRunner类的run方法,这个方法会闯入一个新建的journalnode对象和传入main方法args。这个run方法如下:

public static int run(Configuration conf, Tool tool, String[] args) 
    throws Exception{
    if(conf == null) {
      conf = new Configuration();
    }
    GenericOptionsParser parser = new GenericOptionsParser(conf, args);
    //set the configuration back, so that Tool can configure itself
    tool.setConf(conf);

    //get the args w/o generic hadoop args
    String[] toolArgs = parser.getRemainingArgs();
    return tool.run(toolArgs);
  }

  这里的重点在于最后一行,调用tool的run方法。这里的tool实际是传入的journalnode对象。其run方法如下:

public int run(String[] args) throws Exception {
    start();
    return join();
  }

  这里主要调用的是start方法,方法内容如下:

public void start() throws IOException {
    Preconditions.checkState(!isStarted(), "JN already running");

    validateAndCreateJournalDir(localDir);

    DefaultMetricsSystem.initialize("JournalNode");
    JvmMetrics.create("JournalNode",
        conf.get(DFSConfigKeys.DFS_METRICS_SESSION_ID_KEY),
        DefaultMetricsSystem.instance());

    InetSocketAddress socAddr = JournalNodeRpcServer.getAddress(conf);
    SecurityUtil.login(conf, DFSConfigKeys.DFS_JOURNALNODE_KEYTAB_FILE_KEY,
        DFSConfigKeys.DFS_JOURNALNODE_KERBEROS_PRINCIPAL_KEY, socAddr.getHostName());

    registerJNMXBean();

    httpServer = new JournalNodeHttpServer(conf, this);
    httpServer.start();

    httpServerURI = httpServer.getServerURI().toString();

    rpcServer = new JournalNodeRpcServer(conf, this);
    rpcServer.start();
  }

  与RPC相关的在第22行,这里创建了一个JournalNodeRpcServer对象。这个方法的构造方法如下:

JournalNodeRpcServer(Configuration conf, JournalNode jn) throws IOException {
    this.jn = jn;

    Configuration confCopy = new Configuration(conf);

    // Ensure that nagling doesn't kick in, which could cause latency issues.
    confCopy.setBoolean(
        CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_KEY,
        true);

    InetSocketAddress addr = getAddress(confCopy);
    RPC.setProtocolEngine(confCopy, QJournalProtocolPB.class,
        ProtobufRpcEngine.class);
    QJournalProtocolServerSideTranslatorPB translator =
        new QJournalProtocolServerSideTranslatorPB(this);
    BlockingService service = QJournalProtocolService
        .newReflectiveBlockingService(translator);

    this.server = new RPC.Builder(confCopy)
      .setProtocol(QJournalProtocolPB.class)
      .setInstance(service)
      .setBindAddress(addr.getHostName())
      .setPort(addr.getPort())
      .setNumHandlers(HANDLER_COUNT)
      .setVerbose(false)
      .build();

    // set service-level authorization security policy
    if (confCopy.getBoolean(
      CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
          server.refreshServiceAcl(confCopy, new HDFSPolicyProvider());
    }
  }

  首先是第12行的setProtocolEngine方法,这个方法在文档(14)中介绍过,其主要作用是设置接口使用的engine类。然后是第14行到第17行,创建一个service用来处理远程调用的方法。最后是第19行到第26行创建rpc server。

  这里RPC server的方式与文档(13)使用的方式相同。首先创建一个Builder类,然后向builder中设置需要的参数,最后再调用build方法创建server。其中重点在build方法。其内容如下:

public Server build() throws IOException, HadoopIllegalArgumentException {
      if (this.conf == null) {
        throw new HadoopIllegalArgumentException("conf is not set");
      }
      if (this.protocol == null) {
        throw new HadoopIllegalArgumentException("protocol is not set");
      }
      if (this.instance == null) {
        throw new HadoopIllegalArgumentException("instance is not set");
      }

      return getProtocolEngine(this.protocol, this.conf).getServer(
          this.protocol, this.instance, this.bindAddress, this.port,
          this.numHandlers, this.numReaders, this.queueSizePerHandler,
          this.verbose, this.conf, this.secretManager, this.portRangeConfig);
    }
  }

  首先是第2行到第10行,这里主要是检查一些参数。重点在第12行这里先用getProtocolEngine方法获取engine类,然后调用engine类的getServer方法。getProtocolEngine方法在文档(14)中也提到过,它最终获取的类是是上文设置的ProtobufRpcEngine类。这个类的getserver方法内容如下:

@Override
  public RPC.Server getServer(Class<?> protocol, Object protocolImpl,
      String bindAddress, int port, int numHandlers, int numReaders,
      int queueSizePerHandler, boolean verbose, Configuration conf,
      SecretManager<? extends TokenIdentifier> secretManager,
      String portRangeConfig)
      throws IOException {
    return new Server(protocol, protocolImpl, conf, bindAddress, port,
        numHandlers, numReaders, queueSizePerHandler, verbose, secretManager,
        portRangeConfig);
  }

  这里是直接创建了一个server对象,这个server对象是其内部类继承于RPC的server,其内容如下:

public static class Server extends RPC.Server

  这个类的构造方法如下:

public Server(Class<?> protocolClass, Object protocolImpl,
        Configuration conf, String bindAddress, int port, int numHandlers,
        int numReaders, int queueSizePerHandler, boolean verbose,
        SecretManager<? extends TokenIdentifier> secretManager, 
        String portRangeConfig)
        throws IOException {
      super(bindAddress, port, null, numHandlers,
          numReaders, queueSizePerHandler, conf, classNameBase(protocolImpl
              .getClass().getName()), secretManager, portRangeConfig);
      this.verbose = verbose;  
      registerProtocolAndImpl(RPC.RpcKind.RPC_PROTOCOL_BUFFER, protocolClass,
          protocolImpl);
    }

  这里的重点有两个,第一个是第7行的super方法,这个方法会调用父类的构造方法。第二个是第11行的registerProtocolAndImpl方法,这两个方法会将远程调用的接口与其实现类进行封装存储。

  这里先解析registerProtocolAndImpl方法,其内容如下:

void registerProtocolAndImpl(RpcKind rpcKind, Class<?> protocolClass, 
       Object protocolImpl) {
     String protocolName = RPC.getProtocolName(protocolClass);
     long version;


     try {
       version = RPC.getProtocolVersion(protocolClass);
     } catch (Exception ex) {
       LOG.warn("Protocol "  + protocolClass + 
            " NOT registered as cannot get protocol version ");
       return;
     }


     getProtocolImplMap(rpcKind).put(new ProtoNameVer(protocolName, version),
         new ProtoClassProtoImpl(protocolClass, protocolImpl)); 
     LOG.debug("RpcKind = " + rpcKind + " Protocol Name = " + protocolName +  " version=" + version +
         " ProtocolImpl=" + protocolImpl.getClass().getName() + 
         " protocolClass=" + protocolClass.getName());
   }

  这段代码实际不复杂,首先是第3行拿到接口的名称,然后是第8行拿到接口的版本号。然后是第16行先用getProtocolImplMap方拿到存储该接口信息的map,然后使用put方法将数据设置到map中。其中key是封装了接口名称与版本的ProtoNameVer类对象,value是封装了接口与实现类的ProtoClassProtoImpl类对象。这两个类都是单纯用来存储数据的,没有其他特殊方法。

  用来获取map的getProtocolImplMap方法,也很简单,其内容如下:

Map<ProtoNameVer, ProtoClassProtoImpl> getProtocolImplMap(RPC.RpcKind rpcKind) {
     if (protocolImplMapArray.size() == 0) {// initialize for all rpc kinds
       for (int i=0; i <= RpcKind.MAX_INDEX; ++i) {
         protocolImplMapArray.add(
             new HashMap<ProtoNameVer, ProtoClassProtoImpl>(10));
       }
     }
     return protocolImplMapArray.get(rpcKind.ordinal());   
   }

  首先是第2行,如果protocolImplMapArray中没有数据,便根据RpcKind的最大值来向其中添加hashmap。这里的protocolImplMapArray是一个ArrayList。最后是第8行根据传入的rpcKind类获取对应的map。

  然后再看这个server的super方法,这个方法调用的是其父类RPC的server。其内容如下:

protected Server(String bindAddress, int port, 
                     Class<? extends Writable> paramClass, int handlerCount,
                     int numReaders, int queueSizePerHandler,
                     Configuration conf, String serverName, 
                     SecretManager<? extends TokenIdentifier> secretManager,
                     String portRangeConfig) throws IOException {
      super(bindAddress, port, paramClass, handlerCount, numReaders, queueSizePerHandler,
            conf, serverName, secretManager, portRangeConfig);
      initProtocolMetaInfo(conf);
    }

  这里可以看见第7行又调用了父类的构造方法。这个类继承于ipc的Server类,定义如下所示:

public abstract static class Server extends org.apache.hadoop.ipc.Server

  这里创建的server的继承关系如下:首先是engine类ProtobufRpcEngine,在这个类的getServer方法会创建server对象。它创建的server是其内部类Server类的对象,这个类有继承于RPC类的内部类Server类。然后这个Server又继承于ipc包下的Server类。