Hadoop RPC分析 (二) -- Server

来源:互联网 发布:彻底清除软件安装痕迹 编辑:程序博客网 时间:2024/05/17 22:27
[Hadoop RPC服务端:Server]
服务端的基本思路可以简述如下:通过网络获取到远程调用相关的信息,找到对应的方法,执行完成后,将结果通过网络发送给客户端。
对于服务端,我们将主要关心内部的实现机制,找到服务端处理一次远程调用的整个流程。


与Server相关的类主要为下面几个(都是Server的内部类)
Server.Listener & Server.Listener.Reader
将Listener和Reader放在一起,主要是因为Listener和Reader在一起合作,使用Java NIO来实现的网络请求的监听和接收。简单说来,就是Listener线程负责监听来自客户端的socket连接请求,做accept请求操作。然后将建立连接的channel注册到Reader上,由Reader来负责处理数据到达的事件。这里主要使用基于Java NIO实现的网络事件的处理,因此需要对Java NIO有基本的了解,才能读懂这里的代码逻辑。
Server.Connection
代表一个客户端和服务端的连接的访问通道。每个连接到服务端的channel都有一个附加Connection对象,来作为业务上的连接。服务端每次在accept客户端的连接请求的时候,会创建一个Connection对象,将之绑定在这个channel上。
在Connection中会读取数据,将数据反序列化成对应的对象。
Server.Call
表示一次远程调用业务,包含了本次远程过程调用的一些请求参数信息,以及执行完过程调用之后的返回结果信息。
Server.Responder
Responder在server模型中,主要负责远程调用结果的返回,将远程调用的结果通过socket发送给对应的客户端。
Server.Handler
Handler主要负责任务调度。从Server的Call队列中消费远程调用业务,交给Responder返回给客户端。


Server模型比客户端模型负责,借用一张来自(http://langyu.iteye.com/blog/1183337)的图来说明Server这边内部各个组成模块之间的关系




1、Server的入口

Server的入口可以认为是两部分,一个是构造函数生成一个Server对象,一个是start方法启动Server服务
protectedServer(String bindAddress,intport,
      Class<?extendsWritable> rpcRequestClass,inthandlerCount,
     intnumReaders,intqueueSizePerHandler, Configuration conf,
      String serverName, SecretManager<?extendsTokenIdentifier> secretManager,
      String portRangeConfig)
   throwsIOException {
   this.bindAddress= bindAddress;
   this.conf= conf;
   this.portRangeConfig= portRangeConfig;
   this.port= port;
   this.rpcRequestClass= rpcRequestClass;
   this.handlerCount= handlerCount;
   this.socketSendBufferSize= 0;
   this.maxDataLength= conf.getInt(CommonConfigurationKeys.IPC_MAXIMUM_DATA_LENGTH,
        CommonConfigurationKeys.IPC_MAXIMUM_DATA_LENGTH_DEFAULT);
   if(queueSizePerHandler != -1) {
     this.maxQueueSize= queueSizePerHandler;
    }else{
     this.maxQueueSize= handlerCount * conf.getInt(
          CommonConfigurationKeys.IPC_SERVER_HANDLER_QUEUE_SIZE_KEY,
          CommonConfigurationKeys.IPC_SERVER_HANDLER_QUEUE_SIZE_DEFAULT);     
    }
   this.maxRespSize= conf.getInt(
        CommonConfigurationKeys.IPC_SERVER_RPC_MAX_RESPONSE_SIZE_KEY,
        CommonConfigurationKeys.IPC_SERVER_RPC_MAX_RESPONSE_SIZE_DEFAULT);
   if(numReaders != -1) {
     this.readThreads= numReaders;
    }else{
     this.readThreads= conf.getInt(
          CommonConfigurationKeys.IPC_SERVER_RPC_READ_THREADS_KEY,
          CommonConfigurationKeys.IPC_SERVER_RPC_READ_THREADS_DEFAULT);
    }
   this.callQueue  =newLinkedBlockingQueue<Call>(maxQueueSize);
   this.maxIdleTime= 2 * conf.getInt(
        CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_KEY,
        CommonConfigurationKeysPublic.IPC_CLIENT_CONNECTION_MAXIDLETIME_DEFAULT);
   this.maxConnectionsToNuke= conf.getInt(
        CommonConfigurationKeysPublic.IPC_CLIENT_KILL_MAX_KEY,
        CommonConfigurationKeysPublic.IPC_CLIENT_KILL_MAX_DEFAULT);
   this.thresholdIdleConnections= conf.getInt(
        CommonConfigurationKeysPublic.IPC_CLIENT_IDLETHRESHOLD_KEY,
        CommonConfigurationKeysPublic.IPC_CLIENT_IDLETHRESHOLD_DEFAULT);
   this.secretManager= (SecretManager<TokenIdentifier>) secretManager;
   this.authorize=
      conf.getBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION,
                     false);

   // configure supported authentications
   this.enabledAuthMethods= getAuthMethods(secretManager, conf);
   this.negotiateResponse= buildNegotiateResponse(enabledAuthMethods);
   
   // Start the listener here and let it bind to the port
   listener=newListener();
   this.port=listener.getAddress().getPort();   
   this.rpcMetrics= RpcMetrics.create(this);
   this.rpcDetailedMetrics= RpcDetailedMetrics.create(this.port);
   this.tcpNoDelay= conf.getBoolean(
        CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_KEY,
        CommonConfigurationKeysPublic.IPC_SERVER_TCPNODELAY_DEFAULT);

   // Create the responder here
   responder=newResponder();
   
   if(secretManager !=null) {
      SaslRpcServer.init(conf);
    }
   
   this.exceptionsHandler.addTerseExceptions(StandbyException.class);
  }
初始化了Server的内部Call队列,这个队列将会由Handler线程来消费。初始化了Listener对象,初始化了Responder对象。
下面是启动Server的方法,启动Server的内部各个功能模块。
publicsynchronizedvoidstart() {
   responder.start();
   listener.start();
   handlers=newHandler[handlerCount];
   
   for(inti = 0; i <handlerCount; i++) {
     handlers[i] =newHandler(i);
     handlers[i].start();
    }
  }

根据数据的流向,从Listener开始来做流程跟进。Listener和Reader合作,来完成网络连接的建立。

2、Listener

Listener的属性如下。

privateServerSocketChannelacceptChannel=null;//the accept channel
   privateSelectorselector=null;//the selector that we use for the server
   privateReader[]readers=null;
   privateintcurrentReader= 0;
   privateInetSocketAddressaddress;//the address we bind at
   privateRandomrand=newRandom();
   privatelonglastCleanupRunTime= 0;//the last time when a cleanup connec-
                                        //-tion (for idle connections) ran
   privatelongcleanupInterval= 10000;//the minimum interval between
                                         //two cleanup runs
   privateintbacklogLength=conf.getInt(
        CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_KEY,
        CommonConfigurationKeysPublic.IPC_SERVER_LISTEN_QUEUE_SIZE_DEFAULT);


构造Listener对象时的操作,在Listener的构造函数中有体现


publicListener()throwsIOException {
     address=newInetSocketAddress(bindAddress,port);
     // Create a new server socket and set to non blocking mode
     acceptChannel= ServerSocketChannel.open();
     acceptChannel.configureBlocking(false);

     // Bind the server socket to the local host and port
     bind(acceptChannel.socket(),address,backlogLength,conf,portRangeConfig);
     port=acceptChannel.socket().getLocalPort();//Could be an ephemeral port
     // create a selector;
     selector= Selector.open();
     readers=newReader[readThreads];
     for(inti = 0; i <readThreads; i++) {
        Reader reader =newReader(
           "Socket Reader #"+ (i + 1) +" for port "+port);
       readers[i] = reader;
        reader.start();
      }

     // Register accepts on the server socket with the selector.
     acceptChannel.register(selector, SelectionKey.OP_ACCEPT);
     this.setName("IPC Server listener on "+port);
     this.setDaemon(true);
    }

打开一个服务端的channel,绑定服务端口,为服务端channel在Selector对象上注册接受事件。同时,启动了多个Reader线程。在Listener的构造方法中完成了对建立连接端口在selector的监听,这样当有连接请求到达时,就能够做相应的处理。

在Listener的run方法中,会完成处理建立连接请求的事件。以及将channel注册到reader上(在listener上只注册了建立连接的动作,没有注册数据读取动作)。
Listener线程启动之后的主体run方法,主要是进行selector的select操作来获取建立连接的事件,然后调用doAccept方法来建立连接。来看doAccept方法的实现,代码如下

voiddoAccept(SelectionKey key)throwsIOException,  OutOfMemoryError {
      Connection c =null;
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      SocketChannel channel;
     while((channel = server.accept()) !=null) {

        channel.configureBlocking(false);
        channel.socket().setTcpNoDelay(tcpNoDelay);
       
       Reader reader = getReader();
       try{
          reader.startAdd();
         SelectionKey readKey = reader.registerChannel(channel);
         c = newConnection(readKey, channel, Time.now());
         readKey.attach(c);
         synchronized(connectionList) {
           connectionList.add(numConnections, c);
           numConnections++;
          }
         if(LOG.isDebugEnabled())
           LOG.debug("Server connection from " + c.toString() +
               "; # active connections: "+numConnections+
               "; # queued calls: "+callQueue.size());         
        }finally{
          reader.finishAdd();
        }
      }
    }
通过代码中底色标注的部分来看,接收客户端的建立连接的请求,然后将这个建立连接的channel注册到一个Reader线程上。注意这里,在注册时,为每个channel附件了一个Connection对象,这个对象在后面部分会用到。

到这里,客户端和服务端的网络连接已经建立。接着,就是客户端数据请求到达服务端时的处理逻辑,来跟进Reader的代码。Reader线程在Listener构造函数中已经启动,这里直接来看Reader的主体run方法(源码略),只做了调用doRunLoop方法一件事情。跟进doRunLoop方法,其实现也很简单,做一个selector的select操作,然后调用doRead做数据读取。而doRead方法也只是取出和这个channel对应的Connection对象,在这个Connection上对象上调用readAndProcess方法进行处理。

Connection并不是一个线程,它只是表示客户端和服务端连接的业务通道,直接跟进它的readAndProcess方法,这个方法较长,删除了其中一些异常处理分支之后的代码如下
publicintreadAndProcess()
       throwsWrappedRpcServerException, IOException, InterruptedException {
     while(true) {
          count = channelRead(channel,dataLengthBuffer);       
          if(!connectionHeaderRead) {
               count = channelRead(channel,connectionHeaderBuf);
          }
          count = channelRead(channel,data);
          processOneRpc(data.array());
          returncount;
      }
基本上的逻辑就很简单了,从这个Connection对象对应的channel上读数据,然后进行处理。在processOneRpc方法中,做了数据的处理。来跟进这个方法

部分源代码如下
privatevoidprocessOneRpc(byte[] buf)
       throwsIOException, WrappedRpcServerException, InterruptedException {
       finalDataInputStream dis =
           newDataInputStream(newByteArrayInputStream(buf));
       finalRpcRequestHeaderProto header =
            decodeProtobufFromStream(RpcRequestHeaderProto.newBuilder(), dis);
          processRpcRequest(header, dis);
       
    }
去除掉一些异常分支之后,便是处理RPC请求的逻辑:对数据进行反序列化得到请求对象,然后进行处理。来看processRpcRequest的实现,去除一些异常分支之后的代码如下

privatevoidprocessRpcRequest(RpcRequestHeaderProto header,
        DataInputStream dis)throwsWrappedRpcServerException,
        InterruptedException {
      Class<?extendsWritable> rpcRequestClass =
          getRpcRequestWrapper(header.getRpcKind());
      Writable rpcRequest;
      rpcRequest = ReflectionUtils.newInstance(rpcRequestClass,conf);
      rpcRequest.readFields(dis);
      Call call =newCall(header.getCallId(), header.getRetryCount(),
          rpcRequest,this, ProtoUtil.convert(header.getRpcKind()), header
              .getClientId().toByteArray());
     callQueue.put(call);             // queue the call; maybe blocked here
      incRpcCount(); // Increment the rpc count
    }

processRpcRequest方法算是一个起到中间作用的比较重要的方法,将数据流中的二进制数据转成对应的远程调用业务Call对象,然后将Call对象放入Server远程调用业务队列(callQueue,在Server的构造函数中完成的初始化)中.

到这里,我们以Listener开头做的数据追踪,便算到了尽头。要走后续的追踪,就需要回到Server的start方法,去看它的另一个内部组件Handler的实现了(在start方法中,同时启动了Listener和Handler)。

3、Handler

Handler本身继承了Thread类,所以在Server启动的时候,将Handler作为线程来启动,我们关注其主体的run方法。去除一些异常分支之后的代码如下

privateclassHandlerextendsThread {
   

   @Override
   publicvoidrun() {
      ByteArrayOutputStream buf =
       newByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
     while(running) {
       try{
         finalCallcall = callQueue.take();// pop the queue; maybe blocked here
         if(call.connection.user==null) {
             value = call(call.rpcKind,call .connection.protocolName,call.rpcRequestcall.timestamp);
          }else{
              value = call.connection.user.doAs(newPrivilegedExceptionAction<Writable>() {
                    @Override
                    publicWritable run()throwsException {
                      // make the call
                      returncall(call.rpcKind,call.connection.protocolNamecall.rpcRequest,call .timestamp);
                     }
                   }
                  );
            }
         synchronized(call.connection.responseQueue) {
           // setupResponse() needs to be sync'ed together with
           // responder.doResponse() since setupResponse may use
           // SASL to encrypt response data and SASL enforces
           // its own message ordering.
            setupResponse(buf,call, returnStatus, detailedErr,
                value, errorClass, error);
           responder.doRespond(call);
          }
           }
Handler在执行时候,从Server的远程调用业务队列中获取一个Call业务,执行这个远程调用业务(value=call(...)方法实现),设置远程调用业务的返回,然后进行结果返回。

4、Responder

在进行结果返回时,调用了Responder的doRespond,其实只是把要返回的数据添加到了对应的Connection的返回队列中。由于和之前的比较类似,所以,这里就不对Responder做过多的探讨。

主要说明一点,在processResponse方法中,是按照FIFO来从队列中取数据的。在进行数据发送时,如果数据没有一次发送完成,会将调用业务对象重新塞会队列中的第一个位置,用于下一次的继续发送。相关的代码如下所示

    // Processes one response. Returns true if there are no more pending
   // data for this channel.
   //
   privatebooleanprocessResponse(LinkedList<Call> responseQueue,
                                   booleaninHandler)throwsIOException {
    
          call = responseQueue.removeFirst();

          SocketChannel channel = call.connection.channel;
         //
         // Send as much data as we can in the non-blocking fashion
         //
         intnumBytes =channelWrite(channel, call.rpcResponse);
         if(numBytes < 0) {
           returntrue;
          }
         if(!call.rpcResponse.hasRemaining()) {
           
          }else{
           //
           // If we were unable to write the entire response out, then
           // insert in Selector queue.
           //
            call.connection.responseQueue.addFirst(call);
          }
    }

每次都是取队列的第一个元素(removeFirst),执行写操作后,会检查数据是否已经全部发送完成。如果数据还未发送完成,则将对象继续放回队列(call.connection.responseQueue.addFirst(call))。在下一次处理返回结果时,继续发送这个处理结果。避免数据错乱。
1 0
原创粉丝点击