hadoop远程调用

Java远程方法调用

Java远程方法调用(Remote Method Invocation,RMI)是Java的一个核心API和类库,允许一个Java虚拟机上运行的Java程序调用不同虚拟机上运行的对象中的方法,即使这两个虚拟机运行于物理隔离的不同主机上。在某种程度上,RMI可以看成RPC的Java升级版。
Java RMI的开发往往从远程接口的定义开始,本例使用的远程接口如下:

import java.rmi.Remote;
import java.rmi.RemoteException;
//远程接口必须声明为public
//远程接口必须继承自java.rmi.Remote
public interface RMIQueryStatus extends Remote {
   //远程接口中的函数必须将java.rmi.RemoteException声明于其throws子句内
   RMIFileStatus getFileStatus(String filename)
      throws RemoteException;
}

远程接口中函数的实现和普通的成员函数并没有太大的区别(除了它可能抛出java.rmi.RemoteException异常以外)。相关代码如下:

//RMIQueryStatusImpl继承自UnicastRemoteObject,并实现了RMIQueryStatus接口
public class RMIQueryStatusImpl
      extends UnicastRemoteObject implements RMIQueryStatus {
   ……
   @Override
   public RMIFileStatus getFileStatus(String filename) throws ……{
      RMIFileStatus status=new RMIFileStatus(filename);
      ……
      return status;
   }
   ……
}

RMI远端对象注册点rmiregistry是Java自带的一个工具,该应用程序位于Java运行环境的bin目录下。通过下面的命令启动Java RMI注册服务(使用默认端口):

rmiregistry
然后启动服务器RMIQueryStatusServer,该服务器的main方法先创建RMIQueryStatusImpl对象,并与刚才启动的对象注册点对话,将远端对象绑定到名字“rmi://yourserver.com:12090/query”上(应用这个例子的时候,请将这个URI的主机部分改为相应的主机名)。代码如下:

public static void main(String[] args) {
   try {
      //创建RMIQueryStatusImpl对象
      RMIQueryStatusImpl queryService=new RMIQueryStatusImpl();
      LocateRegistry.createRegistry(12090);
      //绑定远端对象到名字
      Naming.rebind(RMI_URL,queryService);
      System.out.println("Server ready");
   } catch(Exception e) {
      e.printStackTrace();
   }
}

客户端RMIQueryStatusClient从远端对象注册点中,通过Naming.lookup()获取远程引用。一旦获取对象引用,并恢复了对象类型,客户端就可以使用此引用,调用远程对象的远程方法,即getFileStatus()方法。相关代码如下:

try {
   //创建RMIQueryStatusImpl对象
   RMIQueryStatus query=
         (RMIQueryStatus)Naming.lookup(RMIQueryStatusServer.RMI_URL);
   //调用远程方法;该调用如同调用本地方法
   RMIFileStatus status=query.getFileStatus("/tmp/testRMI");
   System.out.println(status);
} catch(RemoteException e) {
   //远程方法和普通方法的一个差别:远程方法可能抛出RemoteException异常
   e.printStackTrace();
}

Java RMI实例类图

Java动态代理

Java动态代理类位于java.lang.reflect包下,主要包括java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。
java.lang.reflect.Proxy中比较重要的方法有:

public class Proxy implements java.io.Serializable {
   public static Class<?> getProxyClass(ClassLoader loader,
                                        Class<?>...interfaces)
   ……
   public static Object newProxyInstance(ClassLoader loader,
                                         Class<?>[] interfaces,
                                         InvocationHandler h)
   ……
   public static boolean isProxyClass(Class<?> cl)
   ……
   public static InvocationHandler getInvocationHandler(Object proxy)
   ……
}

其中,Proxy的静态方法getProxyClass()用于获得代理类的java.lang.Class对象。
Proxy类还提供以下方法:
isProxyClass():判断某java.lang.Class对象是否是代理类。
getInvocationHandler():获取代理实例对应的调用处理程序(即构造代理实例时传入的InvocationHandler实例)。
调用InvocationHandler实例的invoke()方法。相关代码如下:

public interface InvocationHandler {
   public Object invoke(Object proxy,Method method,Object[] args)
          throws Throwable;
}

传递给invoke()方法的3个参数分别是:
proxy:代理对象本身。
method:用户调用的代理对象上的方法。
args:传递给该方法的参数。
动态代理实例
org.hadoopinternal.dynamicproxy类图

public interface PDQueryStatus {
   DPFileStatus getFileStatus(String filename);
}
public class DPInvocationHandler implements InvocationHandler {
   private DPQueryStatusImpl dpqs;//目标对象
   //构造函数,传入目标对象
   public DPInvocationHandler(DPQueryStatusImpl dpqs) {
      this.dpqs=dpqs;
   }
   @Override
   public Object invoke(Object proxy,Method method,Object[] args)
            throws Throwable {
      Object ret=null;
      //实现了附加的功能,往控制台输出调用参数的String表示
      String msg1=MessageFormat.format("Calling method {0}({1})",
                                       method.getName(),
                                       Arrays.toString(args));
      System.out.println(msg1);
      //调用转发
      ret=method.invoke(dpqs,args);
      //其他附加功能
      ……
      return ret;
   }
}

静态方法create()用于创建一个到DPQueryStatusImpl实例(也就是目标对象)的代理对象。相关代码如下:

public static PDQueryStatus create(DPQueryStatusImpl dpqs) {
   //调用newProxyInstance需要的Class<?>[]参数
   Class<?>[] interfaces=new Class[] { PDQueryStatus.class };
   //调用newProxyInstance需要的DPInvocationHandler参数
   DPInvocationHandler handler=new DPInvocationHandler(dpqs);
   Object result
           =Proxy.newProxyInstance(dpqs.getClass().getClassLoader(),
                  interfaces,handler);
   ……
   return(PDQueryStatus) result;
}

最终调用DPQueryStatusImpl的同名方法。相关代码如下:

public static void main(String[] args) {
   try {
      PDQueryStatus query=DPMain.create(new DPQueryStatusImpl());
      DPFileStatus status=query.getFileStatus("/tmp/testDP");
      System.out.println(status);
   } catch(Exception e) {
      e.printStackTrace();
   }
}

Hadoop中的远程过程调用

Hadoop IPC接口必须继承自org.apache.hadoop.ipc.VersionedProtocol接口,代码如下:

public interface VersionedProtocol {
   //返回协议接口的版本
   //输入参数是协议接口的类名和客户端的版本号,输出是服务器的协议接口版本号
   public long getProtocolVersion(String protocol,
                                   long clientVersion) throws IOException;
}

org.hadoopinternal.ipc的类图
IPCQueryStatus接口定义如下:

IPC接口必须继承自VersionedProtocol
public interface IPCQueryStatus extends VersionedProtocol {
   IPCFileStatus getFileStatus(String filename);
}

Hadoop中IPC接口的实现中的方法与普通方法没有什么差别。代码如下:

public class IPCQueryStatusImpl implements IPCQueryStatus {
   ……
   @Override
   public IPCFileStatus getFileStatus(String filename) {
      IPCFileStatus status=new IPCFileStatus(filename);
      ……
      return status;
   }
   @Override
   public long getProtocolVersion(String protocol,long clientVersion)
         throws IOException {
      System.out.println("protocol:"+protocol);
      System.out.println("clientVersion:"+clientVersion);
      return IPCQueryServer.IPC_VER;
   }
}

和Java RMI相比,Hadoop IPC建立服务器的方法非常简单,通过org.hadoopinternal.ipc.IPC的静态方法getServer(),就可以在一个IPCQueryStatusImpl实例上,获得一个提供IPCQueryStatus接口功能的IPC服务器。一旦获得服务器实例,就可以通过它的start()方法和stop()方法启动和停止服务器。具体代码如下:

public class IPCQueryServer {
   public static final int IPC_PORT=32121;
   public static final long IPC_VER=5473L;
   public static void main(String[] args) {
      try {
         IPCQueryStatusImpl queryService=new IPCQueryStatusImpl();
         //在对象queryService上获得一个IPC服务器
         Server server=RPC.getServer(queryService,
                "0.0.0.0",IPC_PORT,new Configuration());
         server.start();//服务器开始服务
         ……
         server.stop();//停止服务器
         ……
      } catch(Exception e) {
         e.printStackTrace();
      }
   }
}

访问IPC服务器的客户端代码如下。

public class IPCQueryClient {
   public static void main(String[] args) {
      try {
         //为RPC.getProxy准备Socket地址
         InetSocketAddress addr=new InetSocketAddress("localhost",
               IPCQueryServer.IPC_PORT);
         //通过创建IPC客户端,获得接口IPCQueryStatus的一个实例
         IPCQueryStatus query=(IPCQueryStatus)
               RPC.getProxy(IPCQueryStatus.class,//接口的类对象
                            IPCQueryServer.IPC_VER,//接口版本
                            addr,//服务器地址
                            new Configuration());
         //调用远程方法;该调用如同调用本地方法
         IPCFileStatus status=query.getFileStatus("/tmp/testIPC");
         System.out.println(status);
         //停止客户端
         RPC.stopProxy(query);
      } catch(Exception e) {
         e.printStackTrace();
      }
   }
}

Hadoop IPC的代码结构

共7个文件,包括远程过程调用实现需要的辅助类:
RemoteException:远程异常,应用于IPC客户端,表示远程过程调用中的错误。
Status.java:Status是一个枚举类,定义了远程过程调用的返回结果,包括SUCCESS(成功)、ERROR(一般错误)和FATAL(致命错误)三种情况。
VersionedProtocol接口:前面已经介绍过,Hadoop IPC的远程接口都扩展自VersionedProtocol。
ConnectionHeader.java:IPC客户端与服务器建立连接时发送的消息头。
与客户端、服务器实现相关的代码主要在Client.java、Server.java和RPC.java三个文件中。1)Client.java包含了与IPC客户端相关的代码,如图4-11所示。Client是对IPC客户的抽象,它包含的内部类可以分为与IPC连接相关的,如Client.Connection和Client.ConnectionId等;和远程调用Call相关的如Client.Call、Client.ParallelCall等(后面会详细介绍IPC连接、远程调用Call的相关概念)。
2)Server.java实现了一个IPC服务器端的抽象类。如图4-12所示。
Client.java中包含的主要类
Server.java的类结构图
3)RPC.java在Client.java和Server.java基础上实现了Hadoop IPC。如图4-13所示。
RPC.java中实现的功能主要分为两部分:与客户端相关的功能包括RPC.ClientCache、RPC.Invoker和RPC.Invocation,其中RPC.Invoker继承自前面介绍的调用句柄java.lang.reflect.InvocationHandler。与服务器相关的内部类只有RPC.Server,它是IPC服务器抽象类(定义在Server.java中)的一个具体子类。
RPC.java的类结构
Client.Connection的成员变量

private InetSocketAddress server;//IPC服务器地址
private ConnectionHeader header;//连接消息头
private ConnectionId remoteId;//IPC连接标识
private Socket socket=null;//TCP连接的Socket对象
private DataInputStream in;
private DataOutputStream out;
//当前正在处理的远程调用
private Hashtable<Integer,Call> calls=new Hashtable<Integer,Call>();
//IPC连接的最后一次通信时间
private AtomicLong lastActivity=new AtomicLong();
//连接关闭标记
private AtomicBoolean shouldCloseConnection=new AtomicBoolean();
private IOException closeException;//导致IPC连接关闭的异常

Server.Connection的成员变量

private boolean rpcHeaderRead=false;//状态,是否已经读入RPC版本号
private boolean headerRead=false;//状态,是否已经读入连接消息头
private SocketChannel channel;
private ByteBuffer data;
private ByteBuffer dataLengthBuffer;
private LinkedList<Call> responseQueue;
private volatile int rpcCount=0;//当前正在处理的RPC请求量
private long lastContact;
private int dataLength;
private Socket socket;
//客户端的主机名和端口
private String hostAddress;
private int remotePort;
ConnectionHeader header=new ConnectionHeader();
Class<?> protocol;
Subject user=null;
//一个假的远程调用,作为鉴权失败时的应答
//(Fake 'call' for failed authorization response)
private final int AUTHROIZATION_FAILED_CALLID=-1;
private final Call authFailedCall=
       new Call(AUTHROIZATION_FAILED_CALLID,null,null);
private ByteArrayOutputStream authFailedResponse=
       new ByteArrayOutputStream();

建立IPC连接

连接是在需要的时候,也就是有IPC调用发生的时候才建立的。相关代码如下:

//org.apache.hadoop.ipc.Client的代码片段
private Hashtable<ConnectionId,Connection> connections=
      new Hashtable<ConnectionId,Connection>();
……
private Connection getConnection(InetSocketAddress addr,
                                 Class<?> protocol,
                                 UserGroupInformation ticket,
                                 Call call)
                                 throws IOException {
   ……
   do {
      synchronized(connections) {
         connection=connections.get(remoteId);
         if(connection==null) {
            connection=new Connection(remoteId);
            connections.put(remoteId,connection);
         }
      }
   } while(!connection.addCall(call));
   ……
   connection.setupIOstreams();
}
/org.apache.hadoop.ipc.Client.Connection的代码片段
private class Connection extends Thread {
   ……
   public Connection(ConnectionId remoteId) throws IOException {
      ……
   }
   ……
   private synchronized void setupIOstreams() {
      if(socket !=null||shouldCloseConnection.get()) {
        return;
      }
      ……
      writeHeader();
      //更新连接维护时间
      touch();
      //启动接受线程
      start();
      ……
   }
   ……
   private synchronized boolean addCall(Call call) {
      if(shouldCloseConnection.get())
        return false;
      calls.put(call.id,call);
      notify();
      return true;
   }
   ……
}

Listener.run()实现了NIO中的选择器循环,即调用选择器的select()方法并处理事件,它的处理过程和前面介绍的NIO实例有点差别,这里只通过doRead()方法处理OP_READ,以及通过doAccept()方法处理OP_ACCEPT事件,不关注通道的OP_WRITE事件。

public void run() {
   ……
   while(running) {
      SelectionKey key=null;
      try {
         selector.select();//下面是典型的选择器代码
         Iterator<SelectionKey> iter=selector.selectedKeys().iterator();
         while(iter.hasNext()) {
            key=iter.next();
            iter.remove();
            try {
               if(key.isValid()) {
                 if (key.isAcceptable())
                    doAccept(key);//处理acctpt
                 else if(key.isReadable())
                    doRead(key);//处理read
               }
            } catch(IOException e) {
            }//catch里什么事情都没做
            key=null;
         }//while(iter.hasNext())
      } catch(OutOfMemoryError e) {
         ……//应对没有内存的情况
         closeCurrentConnection(key,e);
         cleanupConnections(true);//后面讨论关闭连接时再介绍
         try { Thread.sleep(60000); } catch(Exception ie) {}
      } catch(InterruptedException e) {
         ……//其他异常情况
      } catch(Exception e) {
         closeCurrentConnection(key,e);
      }
      cleanupConnections(false);
   }
   ……
}

在版本检查阶段,readAndProcess()先读IPC连接魔数和协议版本号,IPC连接魔数一共4字节,正好是缓冲区dataLengthBuffer的长度(这是精心设计的),协议版本号1字节,通过versionBuffer缓冲区读入。

public int readAndProcess() throws IOException,InterruptedException {
   ……
   if(!versionRead) {
     ……
     int version=versionBuffer.get(0);
     dataLengthBuffer.flip();
     if(!HEADER.equals(dataLengthBuffer)||version !=CURRENT_VERSION)
     {
       ……
       return1;//比较IPC连接魔数和协议版本号失败
     }
     ……
   }
   ……
   if(data.remaining()==0) {
     ……
     if(headerRead) {
       ……
     } else {
        processHeader();//处理连接头
        headerRead=true;
        data=null;
        try {//对连接进行鉴权
           authorize(user,header);
           ……
        } catch(AuthorizationException ae) {
           authFailedCall.connection=this;
           setupResponse(authFailedResponse,authFailedCall,
                         Status.FATAL,null,
                         ae.getClass().getName(),ae.getMessage());
           responder.doRespond(authFailedCall);
           return1;//鉴权失败关闭连接
        }
     }
     ……
   }
}
……
private void processHeader() throws IOException {
   DataInputStream in=
         new DataInputStream(new ByteArrayInputStream(data.array()));
   header.readFields(in);
   try {
      String protocolClassName=header.getProtocol();
      if(protocolClassName !=null) {
        protocol=getProtocolClass(header.getProtocol(),conf);
      }
   } catch(ClassNotFoundException cnfe) {
      throw new IOException("Unknown protocol:"+header.getProtocol());
   }
   ……//获取用户信息
}

数据分帧和读写

在IPC客户端中,Connection.sendParam()用于将远程调用Call形成消息发送出去。sendParam()方法首先将要发送的数据写入DataOutputBuffer对象中,以同时获取数据长度和消息内容,然后通过输出流out发送数据。代码如下:

public void sendParam(Call call) {
   ……
   DataOutputBuffer d=null;
   try {
      synchronized(this.out) {
         ……
         //将远程调用Call串行化到一个DataOutputBuffer中
         d=new DataOutputBuffer();
         d.writeInt(call.id);
         call.param.write(d);
         byte[] data=d.getData();
         int dataLength=d.getLength();
         out.writeInt(dataLength);//输出数据长度
         out.write(data,0,dataLength);//输出数据
         out.flush();
      }
   }
   ……
}

服务器端接收数据在Connection.readAndProcess(),上一节已经分析过这个方法的部分代码。与数据分帧相关的代码如下(代码可以分为两部分:读取“显式长度”和读取消息):

public int readAndProcess() throws IOException,InterruptedException {
   while(true) {
      //最多读入一个RPC的数据
      int count=-1;
      if (dataLengthBuffer.remaining() > 0) {
         //读入“显式长度”,dataLengthBuffer一共4字节
         count=channelRead(channel,dataLengthBuffer);
         if (count < 0||dataLengthBuffer.remaining() > 0)
            return count;//“显式长度”还没有读完,或者读过程中出错
      }
      if (!versionRead) {//处理握手过程中的版本信息
         ……
      }
      if (data==null) {
          dataLengthBuffer.flip();
          dataLength=dataLengthBuffer.getInt();//数据长度已读取
          if (dataLength==Client.PING_CALL_ID) {//心跳消息
             dataLengthBuffer.clear();
             return 0;//ping message
          }
          data=ByteBuffer.allocate(dataLength);//为读取数据分配缓冲区
          incRpcCount();//Increment the rpc count
      }
      count=channelRead(channel,data);
      if (data.remaining()==0) {//读到一个完整的消息
         dataLengthBuffer.clear();
         data.flip();
         if (headerRead) {
            processData();//处理数据
            data=null;
            return count;
         } else {
            ……
         }
      }
      return count;
   }
}

维护IPC连接

发送心跳消息使用sendPing()方法,代码如下:

private synchronized void sendPing() throws IOException {
   long curTime=System.currentTimeMillis();
   if ( curTime– lastActivity.get() >=pingInterval) {
      lastActivity.set(curTime);
      synchronized(out) {
         out.writeInt(PING_CALL_ID);//发送–1
         out.flush();
      }
   }
}

PingInputStream继承自FilterInputStream,FilterInputStream提供了在某一特定流的基础上提供附加功能的能力。具体到PingInputStream类,它在原有Socket提供的输入流SocketInputStream上添加心跳检查的能力,代码如下:

private synchronized void setupIOstreams(){
   ……
   this.socket.setSoTimeout(pingInterval);
   ……
}
private class PingInputStream extends FilterInputStream {
   //构造函数,需要一个InputStream
   protected PingInputStream(InputStream in) {
      super(in);
   }
   //处理超时,如果客户端处于运行状态,则调用sendPing()方法
   private void handleTimeout(SocketTimeoutException e) throws…… {
      if (shouldCloseConnection.get()||!running.get()) {
         throw e;
      } else {
         sendPing();
      }
   }
   public int read() throws IOException {
      do {
         try {
            return super.read();
         } catch(SocketTimeoutException e) {
            handleTimeout(e);//处理SocketTimeoutException异常
         }
      } while(true);
   }
   ……
}

服务器端的连接维护相对简单,当接收到客户端的数据时,服务器连接也会更新它的成员变量lastContact(如下):

private synchronized void doRead() throws IOException {
   ……
   c.setLastContact(System.currentTimeMillis());
   ……
}
public void setLastContact(long lastContact) {
   this.lastContact=lastContact;
}

关闭IPC连接

IPC连接上长时间没有发生IPC调用的判断在waitForWork()中,代码如下:

//waitForWork()如果返回false,会导致IPC连接关闭
private synchronized boolean waitForWork() {
   //注意if语句的判断条件,与的条件包括:1)目前没有正在处理的远程调用;
   //2)连接不需要关闭;3)客户端还处于运行状态
   if (calls.isEmpty() && !shouldCloseConnection.get() && running.get())
   {
      long timeout=maxIdleTime-
         (System.currentTimeMillis()-lastActivity.get());//等待时间
      if (timeout>0) {
         try {
            //通过wait等待,可能被到达的数据打断,
            //也可能被Client.stop()打断或超时
            wait(timeout);
         } catch(InterruptedException e) {}
      }
   }
   if (!calls.isEmpty() && !shouldCloseConnection.get() && running.get())
   {
      return true;//需要等待连接上返回的远程调用结果
   } else if(shouldCloseConnection.get()) {
      return false;//shouldCloseConnection置位,关闭连接
   } else if(calls.isEmpty()) {
      markClosed(null);
      return false;//连接长时间处于空闲状态,关闭
   } else {
      markClosed((IOException)new IOException().initCause(
         new InterruptedException()));
      return false;//连接上还有未结束的远程调用,客户端被打断,记录原因
   }
}
……
public void run() {
   ……
   while(waitForWork()) {
      receiveResponse();//如果连接上还有数据要处理,处理数据
   }
   close();//关闭连接
   ……
}

客户端方法调用过程

IPCQueryStatus query=(IPCQueryStatus) RPC.getProxy(IPCQueryStatus.class,
       IPCQueryServer.IPC_VER,addr,new Configuration());
IPCFileStatus status=query.getFileStatus("/tmp/testIPC");

org.apache.hadoop.ipc.Client,即RPC客户端,如代码所示,在构造函数中对象通过ClientCache.getClient()方法获得该成员变量。代码如下:

private static class Invoker implements InvocationHandler {
   ……
   private Client client;
   ……
   public Invoker(InetSocketAddress address,UserGroupInformation ticket,
                  Configuration conf,SocketFactory factory) {
      ……
      this.client=CLIENTS.getClient(conf,factory);
   }
   public Object invoke(Object proxy,Method method,Object[] args)
                        throws Throwable {
      ……
      ObjectWritable value=(ObjectWritable) client.call(
            new Invocation(method,args),address,
            method.getDeclaringClass(),ticket);
      ……
      return value.get();
   }
   /* 关闭IPC客户端,简单调用ClientCache的静态方法stopClient()*/
   synchronized private void close() {
      ……
   }
}

Client.call()方法代码如下:

public Writable call(Writable param,InetSocketAddress addr,
                     Class<?> protocol,UserGroupInformation ticket)
                           throws InterruptedException,IOException {
   Call call=new Call(param);
   Connection connection=getConnection(addr,protocol,ticket,call);
   connection.sendParam(call);//在IPC连接上发送调用的相关信息
   boolean interrupted=false;
   synchronized(call) {
      while(!call.done) {
      try {
         call.wait();//等待结果
      } catch(InterruptedException ie) {
         interrupted=true;//远程调用被打断
      }
   }
   if (interrupted) {
      Thread.currentThread().interrupt();
   }
   if (call.error !=null) {
      if (call.error instanceof RemoteException) {
         call.error.fillInStackTrace();
         throw call.error;//远程调用异常返回,抛异常给本地调用者
      } else {//本地处理出现的异常
         throw wrapException(addr,call.error);
      }
   } else {
      return call.value;//调用正常结束,返回结果
   }
}

Call.callComplete()其他可能的调用均来自Connection.receiveResponse(),如方法名receiveResponse()用于接收服务器发送的调用处理结果。相关代码如下:

private void receiveResponse() {
   ……
   try {
      int id=in.readInt();//获取调用ID
      ……
      Call call=calls.remove(id);
      int state=in.readInt();//远程调用的处理结果
      if (state==Status.SUCCESS.state) {//成功
         Writable value=ReflectionUtils.newInstance(valueClass,conf);
         value.readFields(in);//读取返回值
         call.setValue(value);
      } else if(state==Status.ERROR.state) {//异常
         call.setException(
               new RemoteException(WritableUtils.readString(in),
               WritableUtils.readString(in)));
      } else if(state==Status.FATAL.state) {//致命错误
        markClosed(new RemoteException(WritableUtils.readString(in),
              WritableUtils.readString(in)));//关闭连接
      }
   } catch(IOException e) {
      markClosed(e);
   }
}

服务器端方法调用过程

Listener主要运行NIO选择器循环,并在Listener.doRead()方法中读取数据,在Connection.readAndProcess()中恢复数据帧,然后调用processData()。

private void processData() throws IOException,InterruptedException {
   DataInputStream dis=
      new DataInputStream(new ByteArrayInputStream(data.array()));
   int id=dis.readInt();//调用标识符
   ……
   Writable param=ReflectionUtils.newInstance(paramClass,conf);
   param.readFields(dis);//RPC.Invocation对象
   Call call=new Call(id,param,this);
   callQueue.put(call);//将调用对象放入队列中
}

抽象方法Server.call()通过Writable返回调用的结果,或者通过IOException告知调用过程中发生了异常。看以下代码:

//抽象的call()方法
public abstract Writable call(Class<?> protocol,
      Writable param,long receiveTime)throws IOException;
……
public void run() {
   ……
   ByteArrayOutputStream buf=new ByteArrayOutputStream(10240);
   while(running) {
      try {
         final Call call=callQueue.take();//获取一个远程调用请求
         ……
         String errorClass=null;
         String error=null;
         Writable value=null;
         ……
         try {
            //通过Subject.doAs()调用方法
            value=Subject.doAs(call.connection.user,
                 new PrivilegedExceptionAction<Writable>() {
                    @Override
                    public Writable run() throws Exception {
                       //方法调用
                       return call(call.connection.protocol,
                                   call.param,call.timestamp);
                    }
                 }
               );
            } catch(PrivilegedActionException pae) {
               Exception e=pae.getException();
               errorClass=e.getClass().getName();
               error=StringUtils.stringifyException(e);
            } catch(Throwable e) {
               errorClass=e.getClass().getName();
               error=StringUtils.stringifyException(e);
            }
            ……
            setupResponse(buf,call,
                  (error==null) ? Status.SUCCESS:Status.ERROR,
                  value,errorClass,error);
            responder.doRespond(call);
         } catch(InterruptedException e) {
            ……
         } catch(Exception e) {
            ……
         }
   }
   ……
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值