• Hadoop之RPC


           Hadoop的RPC主要是通过Java的动态代理(Dynamic Proxy)与反射(Reflect)实现,代理类是由java.lang.reflect.Proxy类在运行期时根据接口,采用Java反射功能动态生成的,并且结合java.lang.reflect.InvocationHandler来处理客户端的请求,当用户调用这个动态生成的实现类时,实际上是调用了InvocationHandler实现类的invoke方法。RPC源代码在org.apache.hadoop.ipc下,有以下几个主要类: 
        Client: 客户端,连接服务器、传递函数名和相应的参数、等待结果;
        Server:服务器端,主要接受Client的请求、执行相应的函数、返回结果;
        VersionedProtocol:通信双方所遵循契约的父接口;
        RPC:RPC通信机制,主要是为通信的服务方提供代理。

      1.通信双方遵循的契约

        要通过RPC服务进行通信,服务的提供方必须实现某个接口,而这个即可是VersionedProtocol的子类,诸如:
    InterTrackerProtocol,它是TaskTracker与JobTracker进行通信所遵循的契约,JobTracker是一个Server,它必须实现这个接口;
    JobSubmissionProtocol,它是JobTracker与JobClient通讯所遵循的契约,JobClient利用契约中的方法可以提交作业去执行, 并且得到当前系统的状态;
    DatanodeProtocol,利用此契约,DataNode可以向NameNode汇报自己的块状态以及负载情况。
    InterDatanodeProtocol,DataNode之间利用此契约可以更新数据块。
    其它的接口在此不再一一赘述。

        2.Hadoop中RPC通信原理 

      我们通过TaskTracker与JobTracker的通信来剖析其通信过程,JobTracker的代理是通过下面的方法得到的,
     this.jobClient = (InterTrackerProtocol) 
        UserGroupInformation.getLoginUser().doAs(
            new PrivilegedExceptionAction<Object>() {
          public Object run() throws IOException {
            return RPC.waitForProxy(InterTrackerProtocol.class,
                InterTrackerProtocol.versionID,
                jobTrackAddr, fConf);
          }
        });
    View Code

      它是通过调用RPC类中的静态方法waitForProxy()方法而得到了InterTrackerProtocol的一个代理,借助于这个代理对象,TaskTracker就可以与JobTracker进行通信了。

      VersionedProtocol proxy =
            (VersionedProtocol) Proxy.newProxyInstance(
                protocol.getClassLoader(), new Class[] { protocol },
                new Invoker(protocol, addr, ticket, conf, factory, rpcTimeout));
    View Code

      跟踪Hadoop的源代码,我们可以发现PRC.waitForProxy()最终是调用的Proxy.newProxyInstance()来创建一个代理对象,第一个参数是类加载器(代理类在运行的过程中动态生成),第二个参数是要实现的代理类的接口,第三个参数是InvokercationHandler接口的子类,最终调用的也就是InvokercationHandler实现类的的invoker()方法。

      private static class Invoker implements InvocationHandler {
        private Client.ConnectionId remoteId;
        private Client client;
    .....
    
        public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
          final boolean logDebug = LOG.isDebugEnabled();
          long startTime = 0;
          if (logDebug) {
            startTime = System.currentTimeMillis();
          }
    
          ObjectWritable value = (ObjectWritable)
            client.call(new Invocation(method, args), remoteId);
          if (logDebug) {
            long callTime = System.currentTimeMillis() - startTime;
            LOG.debug("Call: " + method.getName() + " " + callTime);
          }
          return value.get();
        }
        
    ....
      }
    View Code

      我们可以看到,InvocationHandler的实现类Invoker中主要包含两个成员变量即remoteId(唯一标识RPC的服务器端)、Client(通过工厂模式得到的客户端),invoke()方法中最重要的就是下面的语句:

    ObjectWritable value = (ObjectWritable)client.call(new Invocation(method, args), remoteId);
    

      其中call方法的第一个参数封装调用方法和参数并实现Writable接口的对象,以便于在分布式环境中传输,第二个参数勿需多言,它就用于唯一标识RPC Server,也就是与指定的Server进行通信。call方法的核心代码如下:

      public Writable call(Writable param, ConnectionId remoteId)  throws InterruptedException, IOException {
        Call call = new Call(param);
        Connection connection = getConnection(remoteId, call);//请看下面的说明
        connection.sendParam(call);                 // 将参数封装成一个call对象发送给Server
        boolean interrupted = false;
        synchronized (call) {
          while (!call.done) {
            try {
              call.wait();                           // 等待Server发送的内容
            } catch (InterruptedException ie) {
              // save the fact that we were interrupted
              interrupted = true;
            }
          }
    ...
            return call.value;
      }
    View Code

      其中竟然出现了一个Call对象,我们看到此方法返回的结果是call对象的一个成员变量,也就是说Call封装了Client的请求以及Server的响应,synchronized的使用会同步Client的请求以及Server的响应。通Connection对象的sendParam方法可以将请求发送给Server,那么Connection又是什么呢?

       private Connection getConnection(ConnectionId remoteId,Call call) throws IOException, InterruptedException {
        do {
          synchronized (connections) {
            connection = connections.get(remoteId);
            if (connection == null) {
              connection = new Connection(remoteId);
              connections.put(remoteId, connection);
            }
          }
        } while (!connection.addCall(call));
        
    ...
        connection.setupIOstreams();
        return connection;
      }
    View Code

      其实Connection是扩展Thread而得到的一个线程,最终把所有的connection对象都放入到一个Hashtable中,同一个ConnectionId的Connection可以复用,降低了创建线程的开销。connection.setupIOstreams()用于在真正的建立连接,并将RPC的header写入到输出流中,通过start方法启动线程,其核心代码如下所示:

      public void run() {
          while (waitForWork()) {//等到可以读响应时返回true
            receiveResponse();
    }   

      receiveResponse方法主要是从输入流反序列化出value,并将其封装在call对象中,这样client端就得到了server的响应,核心代码如下:

    private void receiveResponse() {
              try {
            int id = in.readInt();                  // 读取连接id,以便从calls中取出相应的call对象
            Call call = calls.get(id);
            int state = in.readInt();     // 读取输入流的状态
            if (state == Status.SUCCESS.state) {
              Writable value = ReflectionUtils.newInstance(valueClass, conf);
              value.readFields(in);                 // read value
              call.setValue(value);
              calls.remove(id);
            } 
    ...
        }
    View Code

    才疏学浅,错误之处在所难免,恳请各位予以指正。。 

  • 相关阅读:
    S3C2440的LCD虚拟显示测试
    arm-linux-gcc编译器测试
    韦东山教程ARM的时钟设置出现的问题及其解决方法
    程序在nor flash中真的可以运行吗?
    存储器的速度
    程序测试的方法
    对编程的一些思考

    [算法题] 字节流解析
    [C/C++]函数指针和函数分发表
  • 原文地址:https://www.cnblogs.com/yueliming/p/3283810.html
Copyright © 2020-2023  润新知