02|tomcat连接器之NIOEndpoint

NIOEndpoint由五部分组成,分别是LimitLatch,Acceptor,Poller,SocketProcessor,Executor。其实现了IO多路复用,也就是建立连接和读取字节流 是在不同的线程中执行,通过selector的死循环一直去监听网卡数据到来的事件,一旦由数据到达,就通过selector读取出来。

  • LimitLatch
    LimitLatch的目的是限制tomcat的连接数,默认值为10000,当连接数到达10000时,后面再过来的请求就需要等待前面的连接数释放了才能被tomcat处理。这里需要注意一点,这只是在应用层限制了连接数,但是操作系统底层还是会建立tcp的连接的。
    LimitLatch是AbstractEndpoint抽象类持有。该类有一个名为Sync的内部类,Sync继承了AbstractQueuedSynchronizer。AbstractQueuedSynchronizer是 Java 并发包中的一个核心类,它在内部维护一个状态和一个线程队列,可以用来控制线程什么时候挂起,什么时候唤醒。JDK中的锁的实现就是基于AbstractQueuedSynchronizer来实现的。
    下面是LimitLatch的核心代码:
    public class LimitLatch {
       private final Sync sync;
       private final AtomicLong count;
       private volatile long limit;
       private volatile boolean released = false;
    
    private class Sync extends AbstractQueuedSynchronizer {
       
       @Override
       protected int tryAcquireShared(int ignored) {
           long newCount = count.incrementAndGet();
           if (!released && newCount > limit) {
               // Limit exceeded
               count.decrementAndGet();
               return -1;
           } else {
               return 1;
           }
       }
    
       @Override
       protected boolean tryReleaseShared(int arg) {
           count.decrementAndGet();
           return true;
       }
    }
    

tryAcquireShared是在请求到达是会调用该方法,当返回值为1时,该请求能继续进行下去;若返回值为-1时,请求就会把放在一个任务队列中。当有线程调用tryReleaseShared才会唤醒队列中的任务重新请求tryAcquireShared获取许可。
在LimitLatch中持有一个AtomicLong类型的 count字段。AtomicLong是一个基于CAS无锁的方式来记录连接数。

  • Acceptor
    Acceptor实现了Runnable接口,因此使得它能跑在一个单独的线程上。Acceptor是用来建立连接,并把到达的连接传送到Poller上,由Poller去读取网络发送的数据。
    下面是Acceptor的核心代码(删减了不重要的代码):
    
    protected class Acceptor extends AbstractEndpoint.Acceptor {
    
          @Override
          public void run() {
              int errorDelay = 0;
              // Loop until we receive a shutdown command
              while (running) {
                  if (!running) {
                      break;
                  }
                  try {
                      //if we have reached max connections, wait
                      countUpOrAwaitConnection();
    
                      SocketChannel socket = null;
                       socket = serverSock.accept();
                      // Successful accept, reset the error delay
                      errorDelay = 0;
    
                      // Configure the socket
                      if (running && !paused) {
                          // setSocketOptions() will hand the socket off to
                          // an appropriate processor if successful
                          if (!setSocketOptions(socket)) {
                              closeSocket(socket);
                          }
                      } else {
                          closeSocket(socket);
                      }
                  } catch (Throwable t) {
                      ExceptionUtils.handleThrowable(t);
                      log.error(sm.getString("endpoint.accept.fail"), t);
                  }
              }
              state = AcceptorState.ENDED;
        }
    

通过代码第7行可以看到,Acceptor会跑在一个死循环内。代码的14行最终就会调用到LimitLatch的tryAcquireShared方法上。然后在代码的第17行通过调用socket的accept方法来接受新连接。然后在26行的setSocketOptions方法内把SocketChannel封装成PollerEvent,并添加到Poller对象内的SynchronizedQueue属性中。SynchronizedQueue是一个加锁的队列,加锁的目的是为了在解决在高并发场景的数据不一致,而队列的目的就是为了让越早到达的请求越快被处理。setSocketOptions的调用链:setSocketOptions()->getPoller0().register()->addEvent()。至此,Acceptor就已经把请求连接交给Poller来处理了。

  • Poller
    Poller也实现了Runnable接口,它也是有个线程跑在死循环上,用来监听所有已经通过Acceptor建立的socket事件,它的作用就像网络编程模型中的Selector,当网卡有数据到达时,会生成SocketProcessor交由线程池Excecutor处理,SocketProcessor也是实现了Runnable接口的类。Poller主要流程的方法调用链:run()->processKey()->processSocket()。processSocket方法的作用是把SocketProcessor交由线程池Excecutor处理。下面是processSocket的源码:
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = processorCache.pop();
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

从代码的第5行可看到,SocketProcessorBase先从processorCache从取出,如果没有则新建一个;如果能获取到,则通过调用reset方法重制该对象。processorCache是SynchronizedStack类型的,之所以使用栈,应该是为了取值更快。然后在代码的14行就把刚才获取到的SocketProcessor交由Executor线程池处理了。

  • SocketProcessor
    SocketProcessor没啥好说的,它主要就是判断了一下请求的连接的条件是否都已满足,若满足之后就通过调用getHandler().process()方法把该请求交由Processor处理了。Processor和SocketProcessor不是同一个东西,SocketProcessor是NIOEndpoint的一个内部类,Processor是和NIOEndpoint同一等级的一个类,主要负责把字节流解析成Tomcat的Request对象。

  • Executor
    Executor 是 Tomcat 定制版的线程池,扩展了Java原生的线程池.
    后面会有单独的章节来解析该线程池

你可能感兴趣的:(java,tomcat)