• hadoop的RPC分析


          hadoop在实现datanode、namenode、client之间的通信时,实现了自己的一套rpc通信的协议,协议服务器端采用nio的方式来处理请求,支持局域网的rpc调用 。

          协议的传输数据采用writeable数据,每次调用将具体函数参数(writeable),调用方法名称,调用参数类型信息传送过去,然后Server端接收到这些参数之后再根据该方法名称,调用参数类型信息得到相应的Method对象,然后使用参数调用 。

          注释源代码见:https://files.cnblogs.com/serendipity/ipc.rar

         实例代码:

         Client端测试代码:

    package com.gemantic.rpc;

    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.ipc.RPC;
    import org.apache.hadoop.net.NetUtils;
    import org.junit.Test;

    import com.gemantic.rpc.RPCServer.RPCTestInterface;

    public class RPCClientTest {

    /**
    *
    @param args
    *
    @throws IOException
    */
    public static void main(String[] args) throws IOException {

    InetSocketAddress nameNodeAddr = NetUtils.createSocketAddr("127.0.0.1:9813");
    RPCTestInterface rpcInterface=(RPCTestInterface)RPC.getProxy(RPCTestInterface.class, RPCTestInterface.versionID, nameNodeAddr, new Configuration());
    rpcInterface.getUper("aedfrtyh");
    System.out.println("--------------------------------------");
    rpcInterface.getUper("dfsgert");
    System.out.println("--------------------------------------");
    rpcInterface.getUper("ertyui");
    System.out.println("--------------------------------------");
    rpcInterface.getUper("qwert");

    }


    }

         Server端测试代码:

    package com.gemantic.rpc;

    import java.io.IOException;

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.ipc.RPC;
    import org.apache.hadoop.ipc.VersionedProtocol;
    import org.apache.hadoop.ipc.RPC.Server;
    import org.junit.Test;




    public class RPCServer {

    /**
    *
    @param args
    *
    @throws IOException
    *
    @throws InterruptedException
    */
    public static void main(String[] args) throws IOException, InterruptedException {
    // TODO Auto-generated method stub

    Server server=RPC.getServer(new RPCTest(), "127.0.0.1", 9813,6, true ,new Configuration());
    server.start();
    server.join();
    }

    //调用接口
    public static interface RPCTestInterface extends VersionedProtocol{
    public static final long versionID = 19L;
    public String getUper(String lowerString);

    }
    //实现
    public static class RPCTest implements RPCTestInterface {

    @Override
    public String getUper(String lowerString) {
    // TODO Auto-generated method stub
    try {
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    return lowerString.toUpperCase();
    }
    @Override
    public long getProtocolVersion(String protocol, long clientVersion)
    throws IOException {
    // TODO Auto-generated method stub
    return versionID;
    }
    }
    @Test
    public void test() {

    }


    }

         Server端类图:

         

         Server端源代码分析:

      

     

         Client端源代码分析:

        

         RPC类图:

        RPC、Client、Server源代码注释以及分析见附件:https://files.cnblogs.com/serendipity/ipc.rar

    源代码片段分析:

    1、Server流程

      Server server=RPC.getServer(new RPCTest(), "127.0.0.1", 9813,6, true ,new Configuration());

    最终调用的Server构造函数
    public Server(Object instance, Configuration conf, String bindAddress,  int port,
                      int numHandlers, boolean verbose) throws IOException {
          //socket地址、端口、RPC调用过程中传输的函数参数(始终为Invocation)、多少线程来处理请求
    super(bindAddress, port, Invocation.class, numHandlers, conf, classNameBase(instance.getClass().getName()));
    //Server的最终实现的实例,Invocation中包含有方法名、方法参数、参数类型,通过反射instance来调用相应的方法 。
    //Server中有多个handler,每个handler都拥有对instance的引用,其不停扫描callQueue来取得call并处理

    this.instance = instance;
          this.implementation = instance.getClass();
    //是否打印调用日志
          this.verbose = verbose;
        }

     protected Server(String bindAddress, int port,
                      Class<? extends Writable> paramClass, int handlerCount,
                      Configuration conf, String serverName)
        throws IOException {
        this.bindAddress = bindAddress;
        this.conf = conf;
        this.port = port;
        this.paramClass = paramClass;
        this.handlerCount = handlerCount;
        this.socketSendBufferSize = 0;
        this.maxQueueSize = handlerCount * MAX_QUEUE_SIZE_PER_HANDLER;
        this.callQueue  = new LinkedBlockingQueue<Call>(maxQueueSize);
        this.maxIdleTime = 2*conf.getInt("ipc.client.connection.maxidletime", 1000);
        this.maxConnectionsToNuke = conf.getInt("ipc.client.kill.max", 10);
        this.thresholdIdleConnections = conf.getInt("ipc.client.idlethreshold", 4000);
        
        // Start the listener here and let it bind to the port
        listener = new Listener();
        this.port = listener.getAddress().getPort();    
        this.rpcMetrics = new RpcMetrics(serverName,
                              Integer.toString(this.port), this);
        this.tcpNoDelay = conf.getBoolean("ipc.server.tcpnodelay", false);


        // Create the responder here
        responder = new Responder();
      }

    2、关于Server中的Response,当handler处理完一个call后会把,结果放入到responseQueue中,放回后会判断responseQueue==1 ?,如果是的话,则立刻将结果返回,或者等到Response线程去扫描responseQueue,然后返回client 。

    3、socket传输数据格式,每个socket在Server的org.apache.hadoop.ipc.Server.Connection中,其读取socket上数据如下:

    //Connection 读取数据
    //每个connection的数据格式:
    //HEADER("hrpc")-CURRENT_VERSION(2)-datalength(ticket的长度)-ticket(前一个length的长度)-datalength(data的长度)-data(长度为前一个长度,data为Invocation的序列化数据)-
    //datalength(data的长度)-data(长度为前一个长度,data为Invocation的序列化数据)-datalength(data的长度)-data(长度为前一个长度,data为Invocation的序列化数据)-......
    //每个连接中HEADER、CURRENT_VERSION、ticket再在连接建立时候发送过来,作为鉴权使用
    //然后即可不断发送data
    public int readAndProcess() throws IOException, InterruptedException {
    System.out.println("-------");
    while (true) {
    /* Read at most one RPC. If the header is not read completely yet
    * then iterate until we read first RPC or until there is no data left.
    */
    int count = -1;
    //dataLengthBuffer读取每次数据的长度,在连接刚建立时候第一次读取的会是"hrpc"
    if (dataLengthBuffer.remaining() > 0) {
    System.out.println("read data length");
    //其实每次读取的dataLengthBuffer均是"hrpc"的byte[]数组
    count = channelRead(channel, dataLengthBuffer);
    if (count < 0 || dataLengthBuffer.remaining() > 0)
    return count;
    }
    if (!versionRead) {
    System.out.println("read versionRead");
    //Every connection is expected to send the header.
    ByteBuffer versionBuffer = ByteBuffer.allocate(1);
    count = channelRead(channel, versionBuffer);
    if (count <= 0) {
    return count;
    }
    int version = versionBuffer.get(0);

    dataLengthBuffer.flip();
    if (!HEADER.equals(dataLengthBuffer) || version != CURRENT_VERSION) {
    //Warning is ok since this is not supposed to happen.
    LOG.warn("Incorrect header or version mismatch from " +
    hostAddress + ":" + remotePort +
    " got version " + version +
    " expected version " + CURRENT_VERSION);
    return -1;
    }
    dataLengthBuffer.clear();
    versionRead = true;
    continue;
    }

    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) {
    System.out.println("read data ");
    processData();
    data = null;
    return count;
    } else {
    System.out.println("read data header ticket");
    processHeader();
    headerRead = true;
    data = null;
    continue;
    }
    }
    return count;
    }
    }

    4、handler

       Server在启动后会启动多个线程,每个线程不停的去callQueue中取call,然后调用instance去处理每个请求,处理完将结果放到responseQueue中或者直接通过response发送到客服端 ,每个这样的线程就叫一个handler 。

    5、Client的调用:java动态代理 。

       

    RPCTestInterface rpcInterface=(RPCTestInterface)RPC.getProxy(RPCTestInterface.class, RPCTestInterface.versionID, nameNodeAddr, new Configuration());
     
    /** Construct a client-side proxy object that implements the named protocol,
       * talking to a server at the named address.
       * 获得相应的代理,获得后会调用getProtocolVersion方法,是以服务器端在你调用实际的方法前会有一个server的方法调用
       * 判断客服端的version和服务端是不是相同? 不同的话则抛出VersionMismatch异常 。
       * 这一块在调用端控制,其实可以修改代码绕过该判断
       * */
      public static VersionedProtocol getProxy(Class<?> protocol,
          long clientVersion, InetSocketAddress addr, UserGroupInformation ticket,
          Configuration conf, SocketFactory factory) throws IOException {    

        VersionedProtocol proxy =
            (VersionedProtocol) Proxy.newProxyInstance(
                protocol.getClassLoader(), new Class[] { protocol },
                new Invoker(addr, ticket, conf, factory));
        long serverVersion = proxy.getProtocolVersion(protocol.getName(),
                                                      clientVersion);
        if (serverVersion == clientVersion) {
          return proxy;
        } else {
          throw new VersionMismatch(protocol.getName(), clientVersion,
                                    serverVersion);
        }
      }
    //客服端调用的动态代理类
     private static class Invoker implements InvocationHandler {
        private InetSocketAddress address;
        private UserGroupInformation ticket;
        private Client client;
        private boolean isClosed = false;

        public Invoker(InetSocketAddress address, UserGroupInformation ticket,
                       Configuration conf, SocketFactory factory) {
          this.address = address;
          this.ticket = ticket;
          this.client = CLIENTS.getClient(conf, factory);
        }

        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), address, ticket);
          if (logDebug) {
            long callTime = System.currentTimeMillis() - startTime;
            LOG.debug("Call: " + method.getName() + " " + callTime);
          }
          return value.get();
        }
        
        /* close the IPC client that's responsible for this invoker's RPCs */
        synchronized private void close() {
          if (!isClosed) {
            isClosed = true;
            CLIENTS.stopClient(client);
          }
        }
      }

    方法调用时候实际上是对每个接口做动态代理,将其对方法的调用转换到Invoker的invoke(Object proxy, Method method, Object[] args)方法中,在该方法中将方法名字和参数包装成new Invocation(method, args)然后将参数发送 。

    6、单个的client调用

       

     public Writable call(Writable param, InetSocketAddress addr, 
    UserGroupInformation ticket)
    throws InterruptedException, IOException {
    Call call = new Call(param);
    Connection connection = getConnection(addr, ticket, call);
    connection.sendParam(call); // send the parameter
    synchronized (call) {
    while (!call.done) {
    try {
    call.wait(); // wait for the result
    } catch (InterruptedException ignored) {}
    }

    if (call.error != null) {
    if (call.error instanceof RemoteException) {
    call.error.fillInStackTrace();
    throw call.error;
    } else { // local exception
    throw wrapException(addr, call.error);
    }
    } else {
    return call.value;
    }
    }
    }

    ///在Call对象上的唤醒代码:

    protected synchronized void callComplete() {
          this.done = true;
          notify();                                 // notify caller
        }

        /** Set the exception when there is an error.
         * Notify the caller the call is done.
         *
         * @param error exception thrown by the call; either local or remote
         */
        public synchronized void setException(IOException error) {
          this.error = error;
          callComplete();
        }
        
        /** Set the return value when there is no error.
         * Notify the caller the call is done.
         *
         * @param value return value of the call.
         */
        public synchronized void setValue(Writable value) {
          this.value = value;
          callComplete();
        }

    单个的调用,此时调用线程发送完call后会等待结果返回,是call上的同步 ,其最终由Connection线程收到response数据后将数据set到call内,唤醒该等待线程,代码见上 。

    7、多个call的批量调用

         client中除了单个的同步调用,还有批量发送call的方法

      

    /** Makes a set of calls in parallel.  Each parameter is sent to the
    * corresponding address. When all values are available, or have timed out
    * or errored, the collected results are returned in an array. The array
    * contains nulls for calls that timed out or errored.
    * 多个请求同时发送,将多个请求打包成ParallelResults,异步发送
    * 只在ParallelResults上等待
    *
    *
    */
    public Writable[] call(Writable[] params, InetSocketAddress[] addresses)
    throws IOException {
    if (addresses.length == 0) return new Writable[0];

    ParallelResults results = new ParallelResults(params.length);
    synchronized (results) {
    for (int i = 0; i < params.length; i++) {
    ParallelCall call = new ParallelCall(params[i], results, i);
    try {
    Connection connection = getConnection(addresses[i], null, call);
    connection.sendParam(call); // send each parameter
    } catch (IOException e) {
    // log errors
    LOG.info("Calling "+addresses[i]+" caught: " +
    e.getMessage(),e);
    results.size--; // wait for one fewer result
    }
    }
    while (results.count != results.size) {
    try {
    results.wait(); // wait for all results
    } catch (InterruptedException e) {}
    }

    return results.values;
    }
    }

    其将所有的call打包成ParallelResults,然后在ParallelResults上等待,但单个call是异步socket发送的,不需要等待签个call的返回 。

    8、



  • 相关阅读:
    01
    py5.30
    py 5.28
    py5.25
    py 5.24
    py 5.22
    py5.21
    py 5.18
    py 5.17
    py 5.16
  • 原文地址:https://www.cnblogs.com/serendipity/p/2243471.html
Copyright © 2020-2023  润新知