hadoop2.7.3源码解析之hadoop RPC使用

概述

在以前的博客中,我简单的介绍了一下hadoop rpc框架的实现流程(http://blog.youkuaiyun.com/zhangjun5965/article/details/59653549),这一小节主要介绍一下在hdfs中,是如何运用这个rpc框架进行通讯的。

首先回顾一下使用hadoop rpc的主要流程

  1. 首先定义client和server交互的接口(如client和namenode的ClientProtocol)
  2. 服务器端实现接口(如namenode提供服务的NameNodeRpcServer类)
  3. 服务器端启动服务。(通过RPC.Builder(conf).build())
  4. 客户端获取接口的代理,执行相关的方法。

下面我们简单讲解一下rpc框架在hadoop的具体使用

namenode提供服务

namenode用于对外提供rpc服务的是NameNodeRpcServe类,这个类实现了NamenodeProtocols接口,NamenodeProtocols定义了NamenodeProtocols需要实现的所有的接口。
比如客户端和namenode交互的ClientProtocol、datanode和namenode交互的DatanodeProtocol,用于提供ha服务的HAServiceProtocol等。


/** The full set of RPC methods implemented by the Namenode.  */
@InterfaceAudience.Private
public interface NamenodeProtocols
  extends ClientProtocol,
          DatanodeProtocol,
          NamenodeProtocol,
          RefreshAuthorizationPolicyProtocol,
          RefreshUserMappingsProtocol,
          RefreshCallQueueProtocol,
          GenericRefreshProtocol,
          GetUserMappingsProtocol,
          HAServiceProtocol,
          TraceAdminProtocol {
}


此外,我们在这个类中看到他的内部定义了两个rpc服务,一个是针对客户端的,一个是针对namenode的



  /** The RPC server that listens to requests from DataNodes */
  private final RPC.Server serviceRpcServer;
  private final InetSocketAddress serviceRPCAddress;

  /** The RPC server that listens to requests from clients */
  protected final RPC.Server clientRpcServer;
  protected final InetSocketAddress clientRpcAddress;

在构造方法中,实例化了这两个变量



      this.serviceRpcServer = new RPC.Builder(conf)
          .setProtocol(
              org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB.class)
          .setInstance(clientNNPbService)
          .setBindAddress(bindHost)
          .setPort(serviceRpcAddr.getPort()).setNumHandlers(serviceHandlerCount)
          .setVerbose(false)
          .setSecretManager(namesystem.getDelegationTokenSecretManager())
          .build();

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


clientNNPbService).setBindAddress(bindHost)
        .setPort(rpcAddr.getPort()).setNumHandlers(handlerCount)
        .setVerbose(false)
        .setSecretManager(namesystem.getDelegationTokenSecretManager()).build();


在start方法中启动了这两个服务。根据代码,发现是在namenode和BackupNode在启动的时候调用了start方法,也就是说这两个服务是在namenode启动的时候初始化的。

这里写图片描述

客户端获取代理

不管是创建文件,还是删除,都是先通过FileSystem.get(conf)来首先获取具体的的文件系统,FileSystem会根据传入的conf来进行适配,比如hdfs://开头的就实例化DistributedFileSystem,最终通过FileSystem.createFileSystem(URI, Configuration)来初始化具体的文件系统。



  private static FileSystem createFileSystem(URI uri, Configuration conf
      ) throws IOException {
    Class<?> clazz = getFileSystemClass(uri.getScheme(), conf);
    FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf);
    fs.initialize(uri, conf);
    return fs;
  }

对于DistributedFileSystem来说, fs.initialize(uri, conf);当然就是调用了
DistributedFileSystem.initialize(URI, Configuration)方法,在这里构造了一个DFSClient对象,客户端所有的操作都是通过它来完成的。


  @Override
  public void initialize(URI uri, Configuration conf) throws IOException {
    super.initialize(uri, conf);
    setConf(conf);

    String host = uri.getHost();
    if (host == null) {
      throw new IOException("Incomplete HDFS URI, no host: "+ uri);
    }
    homeDirPrefix = conf.get(
        DFSConfigKeys.DFS_USER_HOME_DIR_PREFIX_KEY,
        DFSConfigKeys.DFS_USER_HOME_DIR_PREFIX_DEFAULT);

    this.dfs = new DFSClient(uri, conf, statistics);
    this.uri = URI.create(uri.getScheme()+"://"+uri.getAuthority());
    this.workingDir = getHomeDirectory();
  }

在DFSClient的构造方法里,获取了namenode的代理对象,用于和namenode进行交互。


      Preconditions.checkArgument(nameNodeUri != null,
          "null URI");
      proxyInfo = NameNodeProxies.createProxy(conf, nameNodeUri,
          ClientProtocol.class, nnFallbackToSimpleAuth);
      this.dtService = proxyInfo.getDelegationTokenService();
      this.namenode = proxyInfo.getProxy();

在createProxy方法里,通过查询配置文件获取是否配置了HA,来分别获取不同的代理,为了简单理解,我们现在只说一下非HA的情况。非HA的情况是调用了NameNodeProxies.createNonHAProxy(Configuration, InetSocketAddress, Class, UserGroupInformation, boolean, AtomicBoolean)来获取相应的代理。

在这,主要是针对不同的协议来分别获取不同的代理,


  public static <T> ProxyAndInfo<T> createNonHAProxy(
      Configuration conf, InetSocketAddress nnAddr, Class<T> xface,
      UserGroupInformation ugi, boolean withRetries,
      AtomicBoolean fallbackToSimpleAuth) throws IOException {
    Text dtService = SecurityUtil.buildTokenService(nnAddr);

    T proxy;
    if (xface == ClientProtocol.class) {
      proxy = (T) createNNProxyWithClientProtocol(nnAddr, conf, ugi,
          withRetries, fallbackToSimpleAuth);
    } else if (xface == JournalProtocol.class) {
      proxy = (T) createNNProxyWithJournalProtocol(nnAddr, conf, ugi);
    } else if (xface == NamenodeProtocol.class) {
      proxy = (T) createNNProxyWithNamenodeProtocol(nnAddr, conf, ugi,
          withRetries);
    } else if (xface == GetUserMappingsProtocol.class) {
      proxy = (T) createNNProxyWithGetUserMappingsProtocol(nnAddr, conf, ugi);
    } else if (xface == RefreshUserMappingsProtocol.class) {
      proxy = (T) createNNProxyWithRefreshUserMappingsProtocol(nnAddr, conf, ugi);
    } else if (xface == RefreshAuthorizationPolicyProtocol.class) {
      proxy = (T) createNNProxyWithRefreshAuthorizationPolicyProtocol(nnAddr,
          conf, ugi);
    } else if (xface == RefreshCallQueueProtocol.class) {
      proxy = (T) createNNProxyWithRefreshCallQueueProtocol(nnAddr, conf, ugi);
    } else {
      String message = "Unsupported protocol found when creating the proxy " +
          "connection to NameNode: " +
          ((xface != null) ? xface.getClass().getName() : "null");
      LOG.error(message);
      throw new IllegalStateException(message);
    }

    return new ProxyAndInfo<T>(proxy, dtService, nnAddr);
  }

对于ClientProtocol接口来说,通过createNNProxyWithClientProtocol方法来获取,进入这个方法。


  private static ClientProtocol createNNProxyWithClientProtocol(
      InetSocketAddress address, Configuration conf, UserGroupInformation ugi,
      boolean withRetries, AtomicBoolean fallbackToSimpleAuth)
      throws IOException {
    RPC.setProtocolEngine(conf, ClientNamenodeProtocolPB.class, ProtobufRpcEngine.class);

    final RetryPolicy defaultPolicy = 
        RetryUtils.getDefaultRetryPolicy(
            conf, 
            DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_KEY, 
            DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_DEFAULT, 
            DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_KEY,
            DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_DEFAULT,
            SafeModeException.class);

    final long version = RPC.getProtocolVersion(ClientNamenodeProtocolPB.class);

    //首先获取了一个ClientNamenodeProtocolPB代理。

    ClientNamenodeProtocolPB proxy = RPC.getProtocolProxy(
        ClientNamenodeProtocolPB.class, version, address, ugi, conf,
        NetUtils.getDefaultSocketFactory(conf),
        org.apache.hadoop.ipc.Client.getTimeout(conf), defaultPolicy,
        fallbackToSimpleAuth).getProxy();

    //是否有重试机制
    if (withRetries) { // create the proxy with retries

      Map<String, RetryPolicy> methodNameToPolicyMap 
                 = new HashMap<String, RetryPolicy>();

      ClientProtocol translatorProxy =
        new ClientNamenodeProtocolTranslatorPB(proxy);
      return (ClientProtocol) RetryProxy.create(
          ClientProtocol.class,
          new DefaultFailoverProxyProvider<ClientProtocol>(
              ClientProtocol.class, translatorProxy),
          methodNameToPolicyMap,
          defaultPolicy);
    } else {
      return new ClientNamenodeProtocolTranslatorPB(proxy);
    }
  }


我们看到最后都是通过传入ClientNamenodeProtocolPB类型的参数proxy构造了一个ClientNamenodeProtocolTranslatorPB类型的接口代理。

client和namenode交互应该是ClientProtocol,可是为什么要中间再来一个ClientNamenodeProtocolPB接口呢,这就是下面我们要讲的东西了

客户端具体的发送数据流程

ClientNamenodeProtocolPB序列化相应的方法

client和namenode交互使用的是ClientProtocol接口,但是这个接口里面的方法的参数是java的类型,是无法在网络上传输的,所以需要进行序列化操作。所以就有了在客户端序列化操作的ClientNamenodeProtocolPB接口,ClientNamenodeProtocolPB采用了适配器模式对ClientProtocol对应的方法进行了一一的适配,将方法转换成可以在网络上传输的序列化之后的格式。

我们以delete方法为例,当调用了ClientProtocol的方法进行删除文件操作的时候,是先进入了oClientNamenodeProtocolTranslatorPB.delete(String, boolean)方法,用传进来的参数构造了一个DeleteRequestProto用于网络传输的对象。然后通过ClientNamenodeProtocolPB的delete方法来执行相应的操作。

发送序列化之后的数据

然后会在ClientNamenodeProtocolPB相应的代理的invoke方法里,通过client的call方法往服务器发送数据。

服务端反序列化

同样在服务器端也有一个用于反序列化的ClientNamenodeProtocolServerSideTranslatorPB类,用于将server接收的数据进行反序列化,然后在调用NameNodeRpcServer相应的方法执行相应的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值