HDFS源码分析(2)--RPC之Server类

本文详细剖析了HDFS中RPC Server的工作原理,包括Listener、Reader、Connection、Handler、Call和Responder等关键组件的角色和交互过程,揭示了RPC服务端如何处理客户端请求并响应的过程。
摘要由CSDN通过智能技术生成

一、上一节说到了RPC中的通信问题,RPC中的通信分为服务端Server和客户端Client。服务端监听接受客户端传递的信息并处理返回。那么在RPC中服务类Server是怎么实现的呢。接下来为你解析。首先来了解一下Server类中的一些内部类。

从上节可以看到Server类中有许多内部类,这其中有几个是比较重要的,我把它们列举出来,接下来举个例子说明一下它们的作用。

  • Call、RpcCall
  • Listener
  • Connection
  • Handler
  • Reader
  • ConnectionManager
  • Responder

这里用一个场景来解释一下这些类的作用,我们用客服电话来举例,我们这里假设客服电话所有的操作都是由人来完成。首先客服电话对外肯定是同一个号码,比如我们打移动客服都是打10086,打电信客服都是打10000号一样,所以在Server类中也是对外提供一个固定服务地址。当有人打客服电话时,会专门有台电话去接入来电,这就是Listener类的作用,它专门用于监听客户端的连接请求。当客户电话接入之后,会随机分发给一个客服人员进行电话接听,这里的业务员就是Reader。注意,客服公司不可能为每个来电都去聘请一个新的客服。所以在RPC中也不可能为每一个连接创建一个Reader。所以每个Reader需要对应多个客服端。这就需要为每个连接信息进行保存,这里使用Connection类来保存连接信息,每个连接都实例化一个Connection,并使用ConnectManager进行管理。在客服电话场景中,当客服员接到一个客户的需求时,她会将这个需求保证成一个邮件或者工单发给业务处理人员,业务处理人员读取这些邮件或者工单然后去处理相关业务。这里的包装客户需求的邮件或者工单对应的是Call、RPCCall类,它们的用途就是根据RPC的请求信息构建Call对象。这里的业务处理员对应Handler类,Handler类用于处理具体的RPC请求。在客服电话场景中,业务处理员处理完之后可能会自己直接回复客户,也有可能因为各种原因不能自己回复,所以需要一个专门针对这种情况的回复人员,这就是Responder。用于回复RPC请求结果。


二、Listener

Listener是一个线程类,整个Server类中只有一个Listener线程,用于监听来着客户端的Socket连接请求。

private ServerSocketChannel acceptChannel = null; //the accept channel
private Selector selector = null; //the selector that we use for the server
private Reader[] readers = null;
private int currentReader = 0;
private InetSocketAddress address; //the address we bind at

上述是Listener类的主要属性,对于每个新的连接请求,Listener都会从readers中挑选一个Reader线程来处理。Listener中有个selector,它注册了OP_ACCEPT事件,用于监听连接事件。

 
public Listener() throws IOException {
  address = new InetSocketAddress(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 = new Reader[readThreads];
  for (int i = 0; i < readThreads; i++) {
    Reader reader = new Reader(
        "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);
}

Listener类中的run方法

public void run() {
  LOG.info(Thread.currentThread().getName() + ": starting");
  SERVER.set(Server.this);
  connectionManager.startIdleScan();
  while (running) {
    SelectionKey key = null;
    try {
      getSelector().select();//循环判断是否有新的连接建立请求
      Iterator<SelectionKey> iter = getSelector().selectedKeys().iterator();
      while (iter.hasNext()) {
        key = iter.next();
        iter.remove();
        try {
          if (key.isValid()) {
            if (key.isAcceptable())
              doAccept(key);//调用doAccept方法接受连接请求
          }
        } catch (IOException e) {
        }
        key = null;
      }
    } catch (OutOfMemoryError e) {
      // we can run out of memory if we have too many threads
      // log the event and sleep for a minute and give 
      // some thread(s) a chance to finish
      LOG.warn("Out of Memory in server select", e);
      closeCurrentConnection(key, e);
      connectionManager.closeIdle(true);
      try { Thread.sleep(60000); } catch (Exception ie) {}
    } catch (Exception e) {
      closeCurrentConnection(key, e);
    }
  }
  LOG.info("Stopping " + Thread.currentThread().getName());

  synchronized (this) {
    try {
      acceptChannel.close();
      selector.close();
    } catch (IOException e) { }

    selector= null;
    acceptChannel= null;
    
    // 关闭所有的连接
    connectionManager.stopIdleScan();
    connectionManager.closeAll();
  }
}

doAccept()方法:该方法会接受来自客户端的socket请求并初始化socket连接,然后从readers线程池中选出一个Reader线程来读取客户端的RPC请求。但是每个Reader上会有多个客户端的socket连接,那怎么区分某次请求是那个socket的?这里就用到了Connection类,用它来封装Server与Client之间的Socket连接。

void doAccept(SelectionKey key) throws InterruptedException, IOException,  OutOfMemoryError {
  ServerSocketChannel server = (ServerSocketChannel) key.channel();
  SocketChannel channel;//客户端的socket连接
  while ((channel = server.accept()) != null) {

    channel.configureBlocking(false);
    channel.socket().setTcpNoDelay(tcpNoDelay);
    channel.socket().setKeepAlive(true);
    
    Reader reader = getReader();//从Reader线程池中获取一个Reader
    Connection c = connectionManager.register(channel);//注册该连接,并返回一个Connection,这部分在后面讲解
    // If the connectionManager can't take it, close the connection.
    if (c == null) {
      if (channel.isOpen()) {
        IOUtils.cleanup(null, channel);
      }
      connectionManager.droppedConnections.getAndIncrement();
      continue;
    }
    key.attach(c);  // 将这个Connection连接信息附在key上
    reader.addConnection(c);//将包装之后的Connection连接信息添加到reader中。
  }
}


三、Reader类

Reader也是一个线程类

final private BlockingQueue<Connection> pendingConnections;
private final Selector readSelector;

Reader中有两个属性,一个阻塞队列,用于存放新的Connection,一个Selector。在Reader的构造方法中,阻塞队列选择 LinkedBlockingQueue。可以在配置文件中配置阻塞数。

Reader(String name) throws IOException {
  super(name);

  this.pendingConnections =
      new LinkedBlockingQueue<Connection>(readerPendingConnectionQueue);
  this.readSelector = Selector.open();
}

Reader线程的主循环则是在doRunLoop()方法中实现,doRunLoop()方法会监听当前Reader对象负责的所有客户端连接中是否有新的RPC请求到达。如果有则读取并将其封装成一个Call对象,放入callQueue中等待Handler线程去处理。

private synchronized void doRunLoop() {
  while (running) {
    SelectionKey key = null;
    try {
      // consume as many connections as currently queued to avoid
      // unbridled acceptance of connections that starves the select
      int size = pendingConnections.size();
      for (int i=size; i>0; i--) {
   //对每个新的连接进行注册OP_READ事件
        Connection conn = pendingConnections.take();
        conn.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值