Thrift源码系列----6.TThreadedSelectorServer源码实现

前言

        在上一章我们介绍了Thrift非阻塞型服务的父类AbstractNonblockingServer,因为内部的逻辑较复杂,且源码过多,所以主要以贴源码、添加注解的形式讲解。而本章在探索TThreadedSelectorServer源码时,我们按照AbstractNonblockingServer的serve()方法中的调用顺序来梳理整个过程。

回顾

        研究源码前,我们先回顾下TThreadedSelectorServer的模型图,并对每个模块的功能加以说明,从而更易理解源码。


这里写图片描述


        可以看到,整个服务框架分为以下几个部分:

  1. 一个AcceptThread线程对象,专门用于处理监听socket上的新连接。
  2. 若干个SelectorThread对象专门用于处理业务socket的网络I/O操作,所有网络数据的读写均是由这些线程来完成。
  3. 一个负载均衡器SelectorThreadLoadBalancer对象,主要用于AcceptThread线程接收到一个新socket连接请求时,决定将这个新连接请求分配给哪个SelectorThread线程。
  4. 一个ExecutorService类型的工作线程池,在SelectorThread线程中,监听到有业务socket中有调用请求过来,则将请求读取之后,交个ExecutorService线程池中的线程完成此次调用的具体执行。

TThreadedSelectorServer源码

TThreadedSelectorServer.Args

        首先还是先看看Args类的变化 :

public static class Args extends AbstractNonblockingServerArgs<Args> {

    public int selectorThreads = 2;//用于读写IO操作的线程数
    private int workerThreads = 5;//执行业务的线程池的线程数
    private int stopTimeoutVal = 60;//等待服务停止的检查间格
    private TimeUnit stopTimeoutUnit = TimeUnit.SECONDS;//线程池参数
    private ExecutorService executorService = null;//执行业务的线程池
    private int acceptQueueSizePerThread = 4;//读写IO线程接收请求的队列大小
     //接收 客户端新请求的策略类型
    public static enum AcceptPolicy {
      //已接收的连接请求,需要在线程池中注册,如果线程池已经满了,将立即关闭连接,由于调度将会稍微增加延迟
      FAIR_ACCEPT,
      //忽略线程池的状态,尽可能快的处理 客户端连接
      FAST_ACCEPT
    }

    private AcceptPolicy acceptPolicy = AcceptPolicy.FAST_ACCEPT;//默认使用快速模式

    public Args(TNonblockingServerTransport transport) {
      super(transport);
    }
    //对线程池、读写IO的参数进行校验
    public void validate() {
      if (selectorThreads <= 0) {
        throw new IllegalArgumentException("selectorThreads must be positive.");
      }
      if (workerThreads < 0) {
        throw new IllegalArgumentException("workerThreads must be non-negative.");
      }
      if (acceptQueueSizePerThread <= 0) {
        throw new IllegalArgumentException("acceptQueueSizePerThread must be positive.");
      }
    }
    //省略get set方法
  }

TThreadedSelectorServer的变量

        了解了Args参数后,我们需要清楚TThreadedSelectorServer的成员变量与构造方法,方法的作用这里我们不列出来,调用时再讲解。

  private AcceptThread acceptThread;//用于接收客户端连接的线程
  private final Set<SelectorThread> selectorThreads = new HashSet<SelectorThread>();//处理IO读写操作的线程集合
  private final ExecutorService invoker;//线程池两个功能1.将accept的连接添加给SelectorThread 2.执行业务
  private final Args args;参数对象,

  //构造方法中不多讲,如果没有传入线程池,会创建默认线程池,有兴趣的朋友自己看createDefaultExecutor的实现
  public TThreadedSelectorServer(Args args) {
    super(args);
    args.validate();
    invoker = args.executorService == null ? createDefaultExecutor(args) : args.executorService;
    this.args = args;
  }

在介绍了Args和TThreadedSelectorServer的参数和作用后,我们来开始研究一个服务是如何启动的。

AbstractNonblockingServer.Serve()

        这里还是先回顾下,我们启动服务调用的serve()方法内部是如何调用的。

public void serve() {
    //启动IO线程
    if (!startThreads()) {
      return;
    }
    //开始监听端口,接收客户端请求
    if (!startListening()) {
      return;
    }
    //修改服务状态为正在服务中
    setServing(true);
    //启动服务后的阻塞方法,服务停止后通过
    waitForShutdown();
    //修改服务状态为结束服务
    setServing(false);
    //停止监听端口
    stopListening();
  }

TThreadedSelectorServer.startThreads

        源码如下:

protected boolean startThreads() {
    try {
    //根据读写IO线程数的设置,创建相应的读写IO线程放入集合
      for (int i = 0; i < args.selectorThreads; ++i) {
        selectorThreads.add(new SelectorThread(args.acceptQueueSizePerThread));
      }
      //根据serverTransport创建监控线程,来接收客户端的请求,createSelectorThreadLoadBalancer方法这里不算过多讲解,详情见AcceptThread
      acceptThread = new AcceptThread((TNonblockingServerTransport) serverTransport_,
        createSelectorThreadLoadBalancer(selectorThreads));
      //启动所有的读写IO线程
      for (SelectorThread thread : selectorThreads) {
        thread.start();
      }
      //启动监控线程
      acceptThread.start();
      return true;
    } catch (IOException e) {
      LOGGER.error("Failed to start threads!"&e);
      return false;
    }
  }

        看完源码,我们进一步研究AcceptThread和SelectorThread的功能与联系。

AcceptThread与SelectorThread

protected class AcceptThread extends Thread {
    private final TNonblockingServerTransport serverTransport;//监听端口的Socket
    private final Selector acceptSelector;//Java Nio selector
    private final SelectorThreadLoadBalancer threadChooser;//该类内部拥有一个selectorThreads集合的引用,主要就是每次可以从中获取一个SelectorThread对象

    public AcceptThread(TNonblockingServerTransport serverTransport,
        SelectorThreadLoadBalancer threadChooser) throws IOException {
      this.serverTransport = serverTransport;
      this.threadChooser = threadChooser;
      this.acceptSelector = SelectorProvider.provider().openSelector();
      this.serverTransport.registerSelector(acceptSelector);//会将serverTransport中的serverSocketChannel对象注册到Selector中
    }
    //线程运行方法
    public void run() {
      try {
        //忽略
        if (eventHandler_ != null) {
          eventHandler_.preServe();
        }
    //服务未停止时,不断调用select()
        while (!stopped_) {
          select();
        }
      } catch (Throwable t) {
        LOGGER.error("run() on AcceptThread exiting due to uncaught error", t);
      } finally {
        try {
          acceptSelector.close();//关闭selector
        } catch (IOException e) {
          LOGGER.error("Got an IOException while closing accept selector!", e);
        }
        TThreadedSelectorServer.this.stop();//这一步是为了唤醒SelectorThreads中的读写IO线程
      }
    }
    //唤醒selector
    public void wakeupSelector() {
      acceptSelector.wakeup();
    }
    //不断监测客户端请求连接事件,并处理请求
    private void select() {
      try {
        //等待请求事件
        acceptSelector.select();

        //执行接收到的事件
        Iterator<SelectionKey> selectedKeys = acceptSelector.selectedKeys().iterator();
        while (!stopped_ && selectedKeys.hasNext()) {
          SelectionKey key = selectedKeys.next();
          selectedKeys.remove();

          //非法的忽略
          if (!key.isValid()) {
            continue;
          }
          //处理连接请求
          if (key.isAcceptable()) {
            handleAccept();
          } else {
            LOGGER.warn("Unexpected state in select! " + key.interestOps());
          }
        }
      } catch (IOException e) {
        LOGGER.warn("Got an IOException while selecting!", e);
      }
    }
    //接收一个连接
    private void handleAccept() {
      final TNonblockingTransport client = doAccept();//获取连接
      if (client != null) {
        // Pass this connection to a selector thread
        final SelectorThread targetThread = threadChooser.nextThread();//选出一个SelectThread线程
    //FAST_ACCEPT或无线程池时,直接将连接添加给SelectThread
        if (args.acceptPolicy == Args.AcceptPolicy.FAST_ACCEPT || invoker == null) {
          doAddAccept(targetThread, client);
        } else {
          // FAIR_ACCEPT模式下将连接作为一个任务提交给线程池,稍后处理
          try {
            invoker.submit(new Runnable() {
              public void run() {
                doAddAccept(targetThread, client);
              }
            });
          } catch (RejectedExecutionException rx) {
            LOGGER.warn("ExecutorService rejected accept registration!", rx);
            client.close();
          }
        }
      }
    }
    //从serverTransport中接收一个请求
    private TNonblockingTransport doAccept() {
      try {
        return (TNonblockingTransport) serverTransport.accept();
      } catch (TTransportException tte) {
        // something went wrong accepting.
        LOGGER.warn("Exception trying to accept!", tte);
        return null;
      }
    }
    //将新连接添加到SelectorThread的队列中
    private void doAddAccept(SelectorThread thread, TNonblockingTransport client) {
      if (!thread.addAcceptedConnection(client)) {
        client.close();
      }
    }
  }

        SelectorThread是继承了AbstractSelectThread,所以AbstractSelectThread的方法可调用,忘记AbstractSelectThread方法作用的同学请回顾上一篇文章。

protected class SelectorThread extends AbstractSelectThread {
    private final BlockingQueue<TNonblockingTransport> acceptedQueue;//用于保存AcceptThread传过来的 连接

    public SelectorThread() throws IOException {
      this(new LinkedBlockingQueue<TNonblockingTransport>());
    }
    public SelectorThread(int maxPendingAccepts) throws IOException {
      this(createDefaultAcceptQueue(maxPendingAccepts));
    }
    public SelectorThread(BlockingQueue<TNonblockingTransport> acceptedQueue) throws IOException {
      this.acceptedQueue = acceptedQueue;
    }

    //将连接添加到队列中
    public boolean addAcceptedConnection(TNonblockingTransport accepted) {
      try {
        acceptedQueue.put(accepted);
      } catch (InterruptedException e) {
        LOGGER.warn("Interrupted while adding accepted connection!", e);
        return false;
      }
      selector.wakeup();//selector唤醒,这步很重要,因为selectorThread会阻塞在select()方法,而一开始就没有任务时会永远阻塞,所以这里要唤醒
      return true;
    }
    //工作循环,处理 IO读写事件,管理连接
    public void run() {
      try {
        //服务未停止时无限循环
        while (!stopped_) {
          select();//处理IO读写
          processAcceptedConnections();//处理队列中的新连接
          processInterestChanges();//处理现有连接 注册的事件修改请求
        }
        //关闭时的清理工作
        for (SelectionKey selectionKey : selector.keys()) {
          cleanupSelectionKey(selectionKey);
        }
      } catch (Throwable t) {
        LOGGER.error("run() on SelectorThread exiting due to uncaught error", t);
      } finally {
        try {
          selector.close();
        } catch (IOException e) {
          LOGGER.error("Got an IOException while closing selector!", e);
        }
        TThreadedSelectorServer.this.stop();//唤醒其他accept thread  和 selector threads
      }
    }

    //处理IO事件
    private void select() {
      try {
        //等待IO事件
        selector.select();

        //处理接收到的IO事件
        Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
        while (!stopped_ && selectedKeys.hasNext()) {
          SelectionKey key = selectedKeys.next();
          selectedKeys.remove();
          //非法key忽略
          if (!key.isValid()) {
            cleanupSelectionKey(key);
            continue;
          }
          if (key.isReadable()) {
            handleRead(key);//处理读事件,父类实现的方法
          } else if (key.isWritable()) {
            handleWrite(key);//处理写事件,父类实现的方法
          } else {
            LOGGER.warn("Unexpected state in select! " + key.interestOps());
          }
        }
      } catch (IOException e) {
        LOGGER.warn("Got an IOException while selecting!", e);
      }
    }
    //处理队列中的连接请求,将他们注册到本地selector中
    private void processAcceptedConnections() {
      // Register accepted connections
      while (!stopped_) {
        TNonblockingTransport accepted = acceptedQueue.poll();
        if (accepted == null) {
          break;
        }
        registerAccepted(accepted);
      }
    }

    //将一个连接封装为一个FrameBuffer
    protected FrameBuffer createFrameBuffer(final TNonblockingTransport trans,
        final SelectionKey selectionKey,
        final AbstractSelectThread selectThread) {
        return processorFactory_.isAsyncProcessor() ?
                  new AsyncFrameBuffer(trans, selectionKey, selectThread) :
                  new FrameBuffer(trans, selectionKey, selectThread);
    }
    //将新连接注册到selector中,并设置为读事件,同时创建一个FrameBuffer与SelectionKey进行绑定
    private void registerAccepted(TNonblockingTransport accepted) {
      SelectionKey clientKey = null;
      try {
        clientKey = accepted.registerSelector(selector, SelectionKey.OP_READ);

        FrameBuffer frameBuffer = createFrameBuffer(accepted, clientKey, SelectorThread.this);

        clientKey.attach(frameBuffer);
      } catch (IOException e) {
        //清理工作
        LOGGER.warn("Failed to register accepted connection to selector!", e);
        if (clientKey != null) {
          cleanupSelectionKey(clientKey);
        }
        accepted.close();
      }
    }
  } 

        看完代码相信大家还是一头雾水,照旧,画一幅方法调用图,来理清AcceptThread与SelectorThread的关系。


这里写图片描述


        结合序号我们按一次请求的完整处理流程来讲解:
        1.AcceptThread 开始运行,发现有新请求时,进入2步骤。
        2.获取到新连接,选取一个SelectorThread,并调用doAddAccept方法(备注:这一步可能由线程池来执行doAddAccept,默认模式下由AcceptThread调用该方法)
        3.直接调用addAcceptedConnection方法。
        4.将新连接加入到SelectorThread的队列中。
        5.SelectorThread开始第一次运行。
        6.处理队列中的连接,具体的处理方式调用registerAccepted方法。
        7.将新连接注册到当前SelectorThread线程的Selector中,并封装为FrameBuffer绑定到注册好的SelectorKey上。
        8.SelectorThread第二次运行,发现新连接有数据要读(这里假设一次读完了,然后线程池也将业务执行完毕了,向SelectorThread注册了将由read向write转换的事件)。
        9.第三次运行发现,有事件修改需要处理,则将读通道转为写通道。
        10.第四次运行发现 ,有写事件要处理,这时将结果返回给客户端。处理完后,再次注册事件修改,可能再次转为读事件(客户端长连接),也可能断掉(客户端短连接)
        11.第五次再处理10中的事件修改。
        至此,理清了AcceptThread与SelectorThread的关系后,startThreads()方法也就很容易理解了,serve()中的方法都已在上节介绍过,只有waitForShutdown()是子类实现,下节看该方法的源码;

TThreadedSelectorServer.waitForShutdown

  @Override
  protected void waitForShutdown() {
    try {
      joinThreads();
    } catch (InterruptedException e) {
      // Non-graceful shutdown occurred
      LOGGER.error("Interrupted while joining threads!", e);
    }
    gracefullyShutdownInvokerPool();//处理线程池关闭操作,不详述
  }
  //等待AcceptThread和SelectorThread都停止运行
  protected void joinThreads() throws InterruptedException {
    acceptThread.join();//阻塞到这一步直到AcceptThread的无限循环跳出
    for (SelectorThread thread : selectorThreads) {
      thread.join();//阻塞到这一步直到SelectorThread的无限循环跳出
    }
  }

        还是比较容易理解的,就是起到一个阻塞的作用,直到AcceptThread、SelectorThread线程执行完毕。至次整个启动的过程就讲解完毕了,下面需要介绍几个其他地方用到的方法。

TThreadedSelectorServer.stop

        这个方法是AcceptThead、SelectorThread中任意一个在运行时抛出异常时调用,这个时候认为服务不可用,所以调用该方法。

@Override
  public void stop() {
    stopped_ = true;//停止服务,其他AcceptThead、SelectorThread线程读到时跳出run方法
    stopListening(); //停止接收新请求  

    if (acceptThread != null) {
      acceptThread.wakeupSelector();//可能acceptThread处于阻塞中
    }
    if (selectorThreads != null) {
      for (SelectorThread thread : selectorThreads) {
        if (thread != null)
          thread.wakeupSelector();//可能SelectorThread处于阻塞中
      }
    }
  }

TThreadedSelectorServer.requestInvoke

        该方法是AbstractNonblockingServer中的抽象方法,在handleRead方法中读完数据时调用,来执行业务处理。

//将frameBuffer封装成Runnable对象扔进线程池,这里较易看懂,不详述
protected boolean requestInvoke(FrameBuffer frameBuffer) {
    Runnable invocation = getRunnable(frameBuffer);
    if (invoker != null) {
      try {
        invoker.execute(invocation);
        return true;
      } catch (RejectedExecutionException rx) {
        LOGGER.warn("ExecutorService rejected execution!", rx);
        return false;
      }
    } else {
      // Invoke on the caller's thread
      invocation.run();
      return true;
    }
  }

  protected Runnable getRunnable(FrameBuffer frameBuffer) {
    return new Invocation(frameBuffer);
  }

  class Invocation implements Runnable {
  private final FrameBuffer frameBuffer;

  public Invocation(final FrameBuffer frameBuffer) {
    this.frameBuffer = frameBuffer;
  }

  public void run() {
    frameBuffer.invoke();
  }
}

总结

        本章至此TThreadedSelectorServer的源码介绍完毕,其中理解了AcceptThread与SelectThread的关系,整个服务的运行流程也就非常容易理解,对于细节大家参考源码中的注释即可。

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值