Hadoop NameNode之RPC Server(二)

  下面讲Handler是如何处理Call请求的,简单的说就是利用动态代理做一次函数调用,将返回值发给Responser,然后由Responser发送给客户端,Handler的处理函数如下:

public void run() {
      LOG.info(getName() + ": starting");
      SERVER.set(Server.this);
      //分配返回数据的缓冲区
      ByteArrayOutputStream buf =
        new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
      while (running) {
        try {
          final Call call = callQueue.take(); // 从调用队列中拿一个出来进行处理
          if (LOG.isDebugEnabled())
            LOG.debug(getName() + ": has #" + call.id + " from " +
                      call.connection);
         
          String errorClass = null;
          String error = null;
          Writable value = null;
 
          CurCall.set(call);
          //下面这里开始调用,返回值为value
          try {
            // Make the call as the user viaSubject.doAs, thus associating
            // the call with the Subject
            if (call.connection.user == null) {
              value = call(call.connection.protocol, call.param,
                           call.timestamp);
            } else {
              value =
                call.connection.user.doAs
                  (new PrivilegedExceptionAction<Writable>(){
                     @Override
                     public Writable run() throws Exception {
                       // 这里开始真正的调用
                       return call(call.connection.protocol,
                                   call.param, call.timestamp);
 
                     }
                   }
                  );
            }
          } catch (Throwable e) {
            LOG.info(getName()+", call "+call+": error: " + e, e);
            errorClass =e.getClass().getName();
            error = StringUtils.stringifyException(e);
          }
          CurCall.set(null);
          synchronized (call.connection.responseQueue) {
            // setupResponse() needs to be sync'ed togetherwith
            // responder.doResponse() sincesetupResponse may use
            // SASL to encrypt response data and SASLenforces
            // its own message ordering.
            setupResponse(buf, call,
                        (error == null) ? Status.SUCCESS : Status.ERROR,
                        value, errorClass,error);
          // Discard the large buf and resetit back to
          // smaller size to freeup heap
          if (buf.size() > maxRespSize) {
            LOG.warn("Large response size " + buf.size() + " for call " +
                call.toString());
              buf = new ByteArrayOutputStream(INITIAL_RESP_BUF_SIZE);
            }
            responder.doRespond(call);
          }
        } catch (InterruptedException e) {
          if (running) {                          // unexpected -- log it
            LOG.info(getName() + " caught: " +
                     StringUtils.stringifyException(e));
          }
        } catch (Exception e) {
          LOG.info(getName() + " caught: " +
                   StringUtils.stringifyException(e));
        }
      }
      LOG.info(getName() + ": exiting");
    }
  }

上面代码中,Call对象的调用和返回值的设置是重头戏,这个函数里一坨的日志记录和异常捕获,我们只看关键部分,执行完后返回一个Writable类型的可序列化数据,用于返回。

先看第一个:调用

public Writablecall(Class<?> protocol, Writable param, long receivedTime)
    throws IOException {
      try {
         //这里的param是实现Writable和Invocation接口的,所以可以自由转化
        Invocation call = (Invocation)param;
        if (verbose) log("Call:" + call);
        //获得具体的调用方法,因为我们前台执行的是ls命令,所以这里的函数为getFileInfo
        Method method =
         protocol.getMethod(call.getMethodName(),
                                  call.getParameterClasses());
        method.setAccessible(true);
        //动态代理中常用的功能,记录调用时间,返回类型为Object
        long startTime = System.currentTimeMillis();
        Object value = method.invoke(instance, call.getParameters());
        int processingTime = (int) (System.currentTimeMillis() -startTime);
        int qTime = (int) (startTime-receivedTime);
        if (LOG.isDebugEnabled()) {
          LOG.debug("Served: " + call.getMethodName() +
                    " queueTime= " + qTime +
                    " procesingTime= " + processingTime);
        }
        rpcMetrics.addRpcQueueTime(qTime);
        rpcMetrics.addRpcProcessingTime(processingTime);
        rpcMetrics.addRpcProcessingTime(call.getMethodName(),processingTime);
        if (verbose) log("Return:"+value);
        //创建返回值并返回
        return new ObjectWritable(method.getReturnType(), value);
 
      } catch (InvocationTargetException e) {
        Throwable target =e.getTargetException();
        if (target instanceof IOException) {
          throw (IOException)target;
        } else {
          IOException ioe = new IOException(target.toString());
         ioe.setStackTrace(target.getStackTrace());
          throw ioe;
        }
      } catch (Throwable e) {
        if (!(e instanceof IOException)) {
          LOG.error("Unexpected throwable object ", e);
        }
        IOException ioe = new IOException(e.toString());
        ioe.setStackTrace(e.getStackTrace());
        throw ioe;
      }
    }
  }

看返回值的处理,在上面函数执行完后,会将返回值存放在Call对象的Response成员中

private void setupResponse(ByteArrayOutputStreamresponse,
                             Call call, Statusstatus,
                             Writable rv,String errorClass, String error)
  throws IOException {
    response.reset();
DataOutputStream out = new DataOutputStream(response);
//调用编号、成功状态、错误类型一个都不能少
    out.writeInt(call.id);                // write call id
    out.writeInt(status.state);          // write status
 
if (status == Status.SUCCESS) {
  //返回值的写入
      rv.write(out);
    } else {
      WritableUtils.writeString(out,errorClass);
      WritableUtils.writeString(out,error);
    }
    if (call.connection.useWrap) {
      wrapWithSasl(response, call);
}
//复制给Call对象的成员变量,然后就剩返回了
    call.setResponse(ByteBuffer.wrap(response.toByteArray()));
  }
 
   上面函数执行完后,会进入responder.doRespond(call)中,在这里主要会调用Responser的processResponse进行数据返回的操作,调用关系不如上图来的直接,所以直接贴图了,呵呵


void doRespond(Call call) throws IOException{
      synchronized (call.connection.responseQueue) {
        //注意,这里会把call对象放入返回队列哦
        call.connection.responseQueue.addLast(call);
        if (call.connection.responseQueue.size() == 1) {
          //开始处理返回数据
          processResponse(call.connection.responseQueue, true);
        }
      }
}

再看下真正发送数据的函数

private int channelWrite(WritableByteChannel channel,
                           ByteBuffer buffer) throws IOException {
   
    int count = (buffer.remaining() <= NIO_BUFFER_LIMIT) ?
                 channel.write(buffer) : channelIO(null, channel, buffer);
    if (count > 0) {
      rpcMetrics.incrSentBytes(count);
    }
    return count;
  }

这个函数执行完后,客户端就能看到返回数据了


上面的演示中我们看到了在发送的时候用到了Responser对象,那么为什么还要启动一个Responser线程呢?我们看下这个线程的执行体就能理解了

public void run() {
      LOG.info(getName() + ": starting");
      SERVER.set(Server.this);
      long lastPurgeTime = 0;   // last check for old calls.
 
      while (running) {
        try {
          waitPending();     // If a channel is being registered, wait.
          writeSelector.select(PURGE_INTERVAL);
          Iterator<SelectionKey> iter = writeSelector.selectedKeys().iterator();
          while (iter.hasNext()) {
            SelectionKey key = iter.next();
            iter.remove();
            try {
              //这里会处理SelectionKey的OP_WRITE事件,做异步写操作,
              //其实内部调用函数是一样的,如果网络情况不佳,那么处理OP_WRITE事件
              //能有效降低网络阻塞对服务器的影响
              if (key.isValid() && key.isWritable()){
                  doAsyncWrite(key);//
              }
            } catch (IOException e) {
              LOG.info(getName() + ": doAsyncWrite threw exception " + e);
            }
          }
          long now = System.currentTimeMillis();
          if (now < lastPurgeTime + PURGE_INTERVAL) {
            continue;
          }
          lastPurgeTime = now;
          //
          // 长时间没有处理的Call会被清除
          //
          LOG.debug("Checking for old callresponses.");
          ArrayList<Call> calls;
         
          // get the list of channels from list ofkeys.
          synchronized (writeSelector.keys()) {
            calls = new ArrayList<Call>(writeSelector.keys().size());
            iter = writeSelector.keys().iterator();
            while (iter.hasNext()) {
              SelectionKey key = iter.next();
              Call call =(Call)key.attachment();
              if (call != null && key.channel() == call.connection.channel) {
                calls.add(call);
              }
            }
          }
         
          for(Call call : calls) {
            try {
              doPurge(call, now);
            } catch (IOException e) {
              LOG.warn("Error in purging old calls " + e);
            }
          }
        } catch (OutOfMemoryError e) {
          //
          // we can run out of memory if we have toomany threads
          // log the event and sleep for a minute andgive
          // some thread(s) a chance to finish
          //
          LOG.warn("Out of Memory in server select", e);
          try { Thread.sleep(60000); } catch (Exception ie) {}
        } catch (Exception e) {
          LOG.warn("Exception in Responder " +
                   StringUtils.stringifyException(e));
        }
      }
      LOG.info("Stopping " + this.getName());
    }


 

你可能感兴趣的:(Hadoop NameNode之RPC Server(二))