如图所示, 在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. HDU 4638Group (莫队)

        Group Problem Description There are n men ,every man has an ID(1..n).their ID is unique. Whose ID is ...

      2. NS3 一个小问题

        可能会在执行./waf 命令的时候遇到这个问题,比如我想编译 /home/wasdns/Documents/NS3/ns-3.17/scratch 目录下的一个文件:newnsthree.cpp 编译 ...

      3. Kafka 及 PyKafka 的使用

        1. Kafka 1. 简介 Kafka 是一种分布式的.分区的.多副本的基于发布/订阅的消息系统.它是通过 zookeeper 进行协调,常见可以用于 web/nginx 日志.访问日志.消息服务等 ...

      4. HDU 6103 Kirinriki(尺取法)

        http://acm.hdu.edu.cn/showproblem.php?pid=6103 题意: 给出一个字符串,在其中找两串互不重叠的子串,计算它们之间的dis值,要求dis值小于等于m,求能选 ...

      5. MVC ---- 无法将类型"System.Data.EntityState"隐式转换为"System.Data.Entity.EntityState"

        1.EF 5.0解决方法 先卸载EF:Uninstall-Package EntityFramework -Force 在安装EF5.0:Install-Package EntityFramework ...

      6. Java中带标签的break,continue

        首先不带标签的break,continue 就不介绍了.大家平时用的最多的也就是这样的情况了. 首先Java中没有goto,但是可以利用带标签的break, continue来实现类似的跳转. 首先来 ...

      7. 【Jmeter】Linux(Mac)上使用最新版本Jmeter(5.0)做性能测试

        本文我们一起来学习在Linux(Mac)上利用Jmeter进行性能测试并生成测试报告的方法. 环境准备 JDK 访问这个地址 [JDK11.01],根据实际环境下载一个JDK. Jmeter Jmet ...

      8. php 8小时时间差的解决方法小结

        原来从php5.1.0开始,php.ini里加入了date.timezone这个选项,默认情况下是关闭的 也就是显示的时间(无论用什么php命令)都是格林威治标准时间 和我们的时间(北京时间)差了正好 ...

      9. 女生学java是否真的没有优势

        随着女性越来越独立,我们可以看到再以前我们认为不适合女性朋友从事的工作,也出现了越来越多的女生,例如对IT行业也不再跟之前一样畏惧.虽然当下很多人所持的观点依旧是,女生不适合IT行业,但是很多女生已经 ...

      10. Spring boot 嵌入的tomcat不能启动: Unregistering JMX-exposed beans on shutdown

        原因是:没有引入tomcat依赖包 <dependency> <groupId>org.springframework.boot</groupId> <art ...