Hadoop作为一个存储与服务的基础性平台,它的内部又采用了master/slave架构,那么其内部通信和与客户端的交互就是必不可少了。Hadoop在实现时抛弃了JDK自带的一个RPC实现——RMI,而自己基于IPC模型实现了一个高效的轻量级RPC。


整体结构

    在IPC(inter process call,进程间的通信)包中,最重要的3个类是Server,Client和RPC,它们具有层次化的结构。


  1. 1.RPC
  2.  (1) RPC类是对Server、Client的具体化。
  3. 参数类型必须是Invocation;从服务器返回的值类型必须是ObjectWritable。
  4.  为了加强理解,可以查看测试类TestIPC。在那里,规定的参数类型与返回值类型都是LongWritable。
  5. (2) RPC类是对Server、Client的包装,简化用户的使用。
  6. 如果一个类需充当服务器,只需通过RPC类的静态方法getServer获得Server实例,然后start。同时此类提供协议接口的实现。如果一个类充当客户端,可以通过getProxy或者waitForProxy获得一个实现了协议接口的proxy object,与服务器端交互。
  7.  为了加强理解,可以查看测试类TestRPC,在那里,实现的协议接口为TestProtocol。

2.Server类

  1.  (1).启动Listener进程。
  2.  如果收到需要建立连接的请求,将建立连接,然后在上面捕获读操作的命令。收到命令之后,将把解析客户端发过来信息的工作委派给Connection。Connection把信息封装到Call对象中,放入队列中,待Handler处理。
  3.  (2).启动指定数目的Handler线程,处理客户端对指定方法调用的请求,然后把结果返回给客户端。

 3.Client类

  1.  用Call封装好调用信息,然后借助从连接池中取出的Connection向服务器端发送,等待结果。如果到指定服务器的Connection不存在,将马上建立。Connection线程读取服务器方法调用的返回信息。完成之后,通知主线程。


客户端C要发起向服务端S的关于方法M调用,过程如下:


    1. C首先创建一个通向S的连接getConnection,然后将此次调用放入CallList里,这样客户端就可以同时发生很多调用,每个调用用ID来识别。


    2. 发送调用参数

    调用参数是Client的调用方(比如NameNode,DataNode等)指定的,一般就是一个Invocation对象,里面包含要调用的方法和参数。了解JAVA动态代理类java.lang.reflect.Proxy会对这里的理解有很大帮助。

    3. 等待调用结果

    Client.Connection是个线程类,启动了之后唯一做的时候就是等待调用结果



    对于服务器端,其有一个方法start指定了启动服务器开始监听,这个start被四个类调用,分别是TaskTracker.initialize,Namenode.initialize,Jobtracker.offerService,Datanode.startDatanode显然,任何两者之间的通信都是考这个client-server模型实现的。

    server start后,干了三件事

    1. 启动listen,监听客户端Call

    2. 启动response,随时准备将处理结果发回client

    3. 启动10个handler,处理具体的请求。


    这里必须对

java NIO

机制了解,才能看的明白。这里,可以参考博文:(todo)


当客户端调用来到的时候

   1. listen首先将调用doaccept将Connection附加给selectionkey,然后调用doread添加,doread会调用Connecton的方法将调用添加到调用列表,该列表是BlockingQueue,其保持列表先进先出的特性而且支持同步

   2. listen将call添加到calllist后,handler因为一直在检测calllist,于是其立刻开始处理,处理完毕后,其将结果保存在call对象中,然后调用response开始向客户端写。这里hadler调用的call只是一个未实现的方法,具体实现在RPC.Server中,这点需要注意。

   3. Response也监视responselist,如果responselist中某个call需要将结果写入客户端,就写出,当某个call的结果被发送完毕,从responselist中删除该call对象。


    这里有个细节:handler完成call之后就开始向客户端写call结果,但是结果可能太多,无法通过一次性发送完毕,而发送之后还要等待client接受完毕才能再发,如果现在handler在那里等待客户端接受完毕,然后再发,效率不高。解决办法是handler处理完毕之后,只向client发送一次处理结果。如果这一次将处理结果发送完毕,接下来就没有response的事情了,如果没有发送完毕,接下来response负责将剩下的处理结果发送给客户端。这样handler的并发量会大一些。


    服务器实现中大量利用监视队列,比如handler就直观坚持calllist,一旦发现数据就开始处理,而response就监视responselist,发现数据需要发送就开始发送。