如图所示, 在hadoop中客户端需要和服务端通信 。 首先我们看一下需求是啥。

举一个例子,在客户端想要往hadoop集群中写数据的时候,它需要先和namenode通信,以便获得 诸一个blockID。

这时 ,我们希望在客户端可以做到 诸如 调用一个方法 如 getBlockID() 则就获得了服务端的发过来的ID ,如果调用本地方法一样。

需求搞定,我们看现实有的条件 服务端通信我们有的能力为socket,这个是已经封装在linux内核之中, JAVA对linux内核通信又进行了封装,有了自己的

Socket ServerSocket 通信, 同时在JAVA Nio中又提出了 异步方式的IO。

好,我们有的资源和需要达到的目标都已经有了,下面是实现中间件来弥补两者之间的鸿沟。

首先从客户端来看。 客户端调用服务端的服务,肯定需要底层通信处理,而且这些通信处理需要集中处理,不能每次远程调用,都需重新处理一遍底层连接。

有什么方法可以达到这个目的么 ? 动态代理。

  1. :客户端是怎样给服务端发送数据的? 

    第一句为了完成连接的建立,我们已经分析完毕;而第二句是为了发送数据,呵呵,分析下去,看能不能解决我们的问题呢。下面贴出Client.Connection类的sendParam()方法吧:

    1. :客户端是怎样获取服务端的返回数据的? 

      ,当连接建立时会启动一个线程用于处理服务端返回的数据,我们看看这个处理线程是怎么实现的吧,下面贴出Client.Connection类和Client.Call类中的相关方法吧:

      1. 方法一:
      2.   public void run() {
      3.       ???
      4.       while (waitForWork()) {
      5.         receiveResponse(); //具体的处理方法
      6.       }
      7.       close();
      8.      ???
      9. }
      10.     
      11. 方法二:
      12. private void receiveResponse() {
      13.       if (shouldCloseConnection.get()) {
      14.         return;
      15.       }
      16.       touch();
      17.       try {
      18.         int id = in.readInt(); // 阻塞读取id
      19.         if (LOG.isDebugEnabled())
      20.           LOG.debug(getName() + " got value #" + id);
      21.           Call call = calls.get(id); //在calls池中找到发送时的那个对象
      22.         int state = in.readInt(); // 阻塞读取call对象的状态
      23.         if (state == Status.SUCCESS.state) {
      24.           Writable value = ReflectionUtils.newInstance(valueClass, conf);
      25.           value.readFields(in); // 读取数据
      26.         //将读取到的值赋给call对象,同时唤醒Client等待线程,贴出setValue()代码方法三
      27.           call.setValue(value);
      28.           calls.remove(id); //删除已处理的call
      29.         } else if (state == Status.ERROR.state) {
      30.         ???
      31.         } else if (state == Status.FATAL.state) {
      32.         ???
      33.         }
      34.       } catch (IOException e) {
      35.         markClosed(e);
      36.       }
      37. }
      38.     
      39. 方法三:
      40. public synchronized void setValue(Writable value) {
      41.       this.value = value;
      42.       callComplete(); //具体实现
      43. }
      44. protected synchronized void callComplete() {
      45.       this.done = true;
      46.       notify(); // 唤醒client等待线程
      47.     }

          

      客户端的代码分析就到这里,我们可以发现 ,客户端使用 普通的socket 连接把客户端的方法调用 名称 参数 (形参 和实参) 传递到服务端了。

      下面分析服务端的代码。

      对于ipc.Server,我们先分析一下它的几个内部类吧:

           

      Call :用于存储客户端发来的请求
      Listener : 监听类,用于监听客户端发来的请求,同时Listener内部还有一个静态类,Listener.Reader,当监听器监听到用户请求,便让Reader读取用户请求。
      Responder :响应RPC请求类,请求处理完毕,由Responder发送给请求客户端。
      Connection :连接类,真正的客户端请求读取逻辑在这个类中。
      Handler :请求处理类,会循环阻塞读取callQueue中的call对象,并对其进行操作。

           

      你会发现其实ipc.Server是一个abstract修饰的抽象类。那随之而来的问题就是:hadoop是怎样初始化RPC的Server端的呢?Namenode初始化时一定初始化了RPC的Sever端,那我们去看看Namenode的初始化源码吧:

      1. private void initialize(Configuration conf) throws IOException {
      2.    ???
      3.     // 创建 rpc server
      4.     InetSocketAddress dnSocketAddr = getServiceRpcServerAddress(conf);
      5.     if (dnSocketAddr != null) {
      6.       int serviceHandlerCount =
      7.         conf.getInt(DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_KEY,
      8.                     DFSConfigKeys.DFS_NAMENODE_SERVICE_HANDLER_COUNT_DEFAULT);
      9.       //获得serviceRpcServer
      10.       this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(),
      11.           dnSocketAddr.getPort(), serviceHandlerCount,
      12.           false, conf, namesystem.getDelegationTokenSecretManager());
      13.       this.serviceRPCAddress = this.serviceRpcServer.getListenerAddress();
      14.       setRpcServiceServerAddress(conf);
      15. }
      16. //获得server
      17.     this.server = RPC.getServer(this, socAddr.getHostName(),
      18.         socAddr.getPort(), handlerCount, false, conf, namesystem
      19.         .getDelegationTokenSecretManager());
      20.     
      21.    ???
      22.     this.server.start(); //启动 RPC server Clients只允许连接该server
      23.     if (serviceRpcServer != null) {
      24.       serviceRpcServer.start(); //启动 RPC serviceRpcServer 为HDFS服务的server
      25.     }
      26.     startTrashEmptier(conf);
      27.   }
        1.     this.serviceRpcServer = RPC.getServer(this, dnSocketAddr.getHostName(),
      28.           dnSocketAddr.getPort(), serviceHandlerCount,

          

      这里面我们需要重点关注的是这个上面这个方法, 可以看到这里面传递过去的第一个参数是this .我们在前面说服务端最终是需要调用在服务端的某个对象来实际运行方法的。

      现在这个this对象,及namenode对象就是服务端的相应对象。我们就有疑问,那么客户端有那么多接口 ,namenode都实现了相应的对象么?是的都实现了。这也好理解,客户端

      会调用什么方法,肯定都是服务端和客户端事先约定好的,服务端肯定把相应的对象创建好了来等待客户端的调用。我们可以看一下namenode实现的端口,就很明晰了。

      1. public class NameNode implements ClientProtocol, DatanodeProtocol,
      2.                                  NamenodeProtocol, FSConstants,
      3.                                  RefreshAuthorizationPolicyProtocol,
      4.                                  RefreshUserMappingsProtocol {

          

      下面我们来分析服务端是如何处理请求的。

      分析过ipc.Client源码后,我们知道Client端的底层通信直接采用了阻塞式IO编程。但hadoop是单中心结构,所以服务端不可以这么做,而是采用了java  NIO来实现Server端,那Server端采用java NIO是怎么建立连接的呢?分析源码得知,Server端采用Listener监听客户端的连接,下面先分析一下Listener的构造函数吧:

      1. public Listener() throws IOException {
      2.   address = new InetSocketAddress(bindAddress, port);
      3.   // 创建ServerSocketChannel,并设置成非阻塞式
      4.   acceptChannel = ServerSocketChannel.open();
      5.   acceptChannel.configureBlocking(false);
      6.     
      7.   // 将server socket绑定到本地端口
      8.   bind(acceptChannel.socket(), address, backlogLength);
      9.   port = acceptChannel.socket().getLocalPort();
      10.   // 获得一个selector
      11.   selector= Selector.open();
      12.   readers = new Reader[readThreads];
      13.   readPool = Executors.newFixedThreadPool(readThreads);
      14.   //启动多个reader线程,为了防止请求多时服务端响应延时的问题
      15.   for (int i = 0; i < readThreads; i++) {
      16.     Selector readSelector = Selector.open();
      17.     Reader reader = new Reader(readSelector);
      18.     readers[i] = reader;
      19.     readPool.execute(reader);
      20.   }
      21.   // 注册连接事件
      22.   acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
      23.   this.setName("IPC Server listener on " + port);
      24.   this.setDaemon(true);
      25. }

      在启动Listener线程时,服务端会一直等待客户端的连接,下面贴出Server.Listener类的run()方法:

      1. public void run() {
      2.    ???
      3.     while (running) {
      4.       SelectionKey key = null;
      5.       try {
      6.         selector.select();
      7.         Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
      8.         while (iter.hasNext()) {
      9.           key = iter.next();
      10.           iter.remove();
      11.           try {
      12.             if (key.isValid()) {
      13.               if (key.isAcceptable())
      14.                 doAccept(key); //具体的连接方法
      15.             }
      16.           } catch (IOException e) {
      17.           }
      18.           key = null;
      19.         }
      20.       } catch (OutOfMemoryError e) {
      21.      ???
      22.   }

      下面贴出Server.Listener类中doAccept ()方法中的关键源码吧:

      1.     void doAccept(SelectionKey key) throws IOException, OutOfMemoryError {
      2.       Connection c = null;
      3.       ServerSocketChannel server = (ServerSocketChannel) key.channel();
      4.       SocketChannel channel;
      5.       while ((channel = server.accept()) != null) { //建立连接
      6.         channel.configureBlocking(false);
      7.         channel.socket().setTcpNoDelay(tcpNoDelay);
      8.         Reader reader = getReader(); //从readers池中获得一个reader
      9.         try {
      10.           reader.startAdd(); // 激活readSelector,设置adding为true
      11.           SelectionKey readKey = reader.registerChannel(channel);//将读事件设置成兴趣事件
      12.           c = new Connection(readKey, channel, System.currentTimeMillis());//创建一个连接对象
      13.           readKey.attach(c); //将connection对象注入readKey
      14.           synchronized (connectionList) {
      15.             connectionList.add(numConnections, c);
      16.             numConnections++;
      17.           }
      18.         ???
      19.         } finally {
      20. //设置adding为false,采用notify()唤醒一个reader,其实代码十三中启动的每个reader都使
      21. //用了wait()方法等待。因篇幅有限,就不贴出源码了。
      22.           reader.finishAdd();
      23.         }
      24.       }
      25.     }

      当reader被唤醒,reader接着执行doRead()方法。

      下面贴出Server.Listener.Reader类中的doRead()方法和Server.Connection类中的readAndProcess()方法源码:

          

      1. 方法一:
      2.  void doRead(SelectionKey key) throws InterruptedException {
      3.       int count = 0;
      4.       Connection c = (Connection)key.attachment(); //获得connection对象
      5.       if (c == null) {
      6.         return;
      7.       }
      8.       c.setLastContact(System.currentTimeMillis());
      9.       try {
      10.         count = c.readAndProcess(); // 接受并处理请求
      11.       } catch (InterruptedException ieo) {
      12.        ???
      13.       }
      14.      ???
      15. }
      16.     
      17. 方法二:
      18. public int readAndProcess() throws IOException, InterruptedException {
      19.       while (true) {
      20.         ???
      21.         if (!rpcHeaderRead) {
      22.           if (rpcHeaderBuffer == null) {
      23.             rpcHeaderBuffer = ByteBuffer.allocate(2);
      24.           }
      25.          //读取请求头
      26.           count = channelRead(channel, rpcHeaderBuffer);
      27.           if (count < 0 || rpcHeaderBuffer.remaining() > 0) {
      28.             return count;
      29.           }
      30.         // 读取请求版本号
      31.           int version = rpcHeaderBuffer.get(0);
      32.           byte[] method = new byte[] {rpcHeaderBuffer.get(1)};
      33.         ???
      34.     
      35.           data = ByteBuffer.allocate(dataLength);
      36.         }
      37.         // 读取请求
      38.         count = channelRead(channel, data);
      39.     
      40.         if (data.remaining() == 0) {
      41.          ???
      42.           if (useSasl) {
      43.          ???
      44.           } else {
      45.             processOneRpc(data.array());//处理请求
      46.           }
      47.         ???
      48.           }
      49.         }
      50.         return count;
      51.       }
      52.     }

      获得call对象 
      下面贴出Server.Connection类中的processOneRpc()方法和processData()方法的源码

      1. 方法一:
      2.  private void processOneRpc(byte[] buf) throws IOException,
      3.         InterruptedException {
      4.       if (headerRead) {
      5.         processData(buf);
      6.       } else {
      7.         processHeader(buf);
      8.         headerRead = true;
      9.         if (!authorizeConnection()) {
      10.           throw new AccessControlException("Connection from " + this
      11.               + " for protocol " + header.getProtocol()
      12.               + " is unauthorized for user " + user);
      13.         }
      14.       }
      15. }
      16. 方法二:
      17.     private void processData(byte[] buf) throws IOException, InterruptedException {
      18.       DataInputStream dis =
      19.         new DataInputStream(new ByteArrayInputStream(buf));
      20.       int id = dis.readInt(); // 尝试读取id
      21.       Writable param = ReflectionUtils.newInstance(paramClass, conf);//读取参数
      22.       param.readFields(dis);
      23.     
      24.       Call call = new Call(id, param, this); //封装成call
      25.       callQueue.put(call); // 将call存入callQueue
      26.       incRpcCount(); // 增加rpc请求的计数
      27.     }

      处理call对象 
      你还记得Server类中还有个Handler内部类吗?呵呵,对call对象的处理就是它干的。下面贴出Server.Handler类中run()方法中的关键代码:

      1. while (running) {
      2.       try {
      3.         final Call call = callQueue.take(); //弹出call,可能会阻塞
      4.         ???
      5.         //调用ipc.Server类中的call()方法,但该call()方法是抽象方法,具体实现在RPC.Server类中
      6.         value = call(call.connection.protocol, call.param, call.timestamp);
      7.         synchronized (call.connection.responseQueue) {
      8.           setupResponse(buf, call,
      9.                       (error == null) ? Status.SUCCESS : Status.ERROR,
      10.                       value, errorClass, error);
      11.            ???
      12.           //给客户端响应请求
      13.           responder.doRespond(call);
      14.         }
      15. }

      终于看到了call 方法 我们下面看看服务端实际的call方法是怎么执行的吧

      1. public Writable call(Class<?> protocol, Writable param, long receivedTime)
      2.     throws IOException {
      3.       try {
      4.         Invocation call = (Invocation)param;
      5.         if (verbose) log("Call: " + call);
      6.     
      7.         Method method =
      8.           protocol.getMethod(call.getMethodName(),
      9.                                    call.getParameterClasses());
      10.         method.setAccessible(true);
      11.     
      12.         long startTime = System.currentTimeMillis();
      13.         Object value = method.invoke(instance, call.getParameters());

      最后一句我们发现实际上是用了反射。 反射中的那个实际对象 instance 就是在namenode起来的时候创建的namenode对象。

      hadoop IPC 源代码分析的更多相关文章

      1. Hadoop源代码分析

        http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

      2. Hadoop源代码分析(完整版)

        Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...

      3. hadoop运行流程分析源代码级

        前言: 最近一直在分析hadoop的运行流程,我们查阅了大量的资料,虽然从感性上对这个流程有了一个认识但是我总是感觉对mapreduce的运行还是没有一个全面的认识,所以决定从源代码级别对mapred ...

      4. Android系统进程间通信(IPC)机制Binder中的Client获得Server远程接口过程源代码分析

        文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6633311 在上一篇文章中,我 们分析了And ...

      5. hadoop源码分析

        hadoop 源代码分析(一) Google 的核心竞争技术是它的计算平台.HadoopGoogle的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http://rese ...

      6. 【转载】linux环境下tcpdump源代码分析

        linux环境下tcpdump源代码分析 原文时间 2013-10-11 13:13:02  CSDN博客 原文链接  http://blog.csdn.net/han_dawei/article/d ...

      7. linux环境下tcpdump源代码分析

        Linux 环境下tcpdump 源代码分析 韩大卫@吉林师范大学 tcpdump.c 是tcpdump 工具的main.c, 本文旨对tcpdump的框架有简单了解,只展示linux平台使用的一部分 ...

      8. Android系统进程Zygote启动过程的源代码分析

        文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6768304 在Android系统中,所有的应用 ...

      9. Android应用程序安装过程源代码分析

        文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6766010 Android系统在启动的过程中, ...

      随机推荐

      1. JavaScript 时间格式

        方法1: Date.prototype.Format = function (fmt) { var o = { , //月份 "d+": this.getDate(), //日 = ...

      2. CMD与AMD区别

      3. 【Coursera】Internet History 小结

        前言 终于看完了接近一半课程的 History 的内容. 在这两周的时间里面,了解了互联网的起源,发展,以及现在互联网的情况.听了许多故事,有让人会心一笑的,也有令人感慨万千的.见到了许多令人景仰的科 ...

      4. Educational Codeforces Round 53 Editorial

        After I read the solution to the problem, I found that my solution was simply unsightly. Solved 4 ou ...

      5. 常见文档一览表 -httpclient

        httpclient http://www.yeetrack.com/?p=779 虫师『性能测试』文章大汇总 https://www.cnblogs.com/fnng/archive/2012/08 ...

      6. tyvj 1038 忠诚 区间最小值 线段树或者rmq

        P1038 忠诚 时间: 1000ms / 空间: 131072KiB / Java类名: Main 描述 老管家是一个聪明能干的人.他为财主工作了整整10年,财主为了让自已账目更加清楚.要求管家每天 ...

      7. Codeforces 832C - Strange Radiation

        832C - Strange Radiation 思路:二分最短时间. 代码: #include<bits/stdc++.h> using namespace std; #define l ...

      8. SqlServer和Oracle判断表和列是否存在

        SqlServer .判断表Users是否存在 if object_id(N'Users',N'U') is not null print '存在' else print '不存在' .判断表User ...

      9. spring-cloud: eureka之:ribbon负载均衡自定义配置(二)

        spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ...

      10. vs2010打包安装

        [WinForm] VS2010发布.打包安装程序(超全超详细) 2017年02月17日 21:47:09 y13156556538 阅读数:16487更多 个人分类: C#winform   1. ...