Apache Tomcat如何高并发处理请求

介绍作为常用的http协议服务器,Tomcat应用非常广泛 。tomcat也是遵循Servelt协议的,Servelt协议可以让服务器与真实服务逻辑代码进行解耦 。各自只需要关注Servlet协议即可 。
对于tomcat是如何作为一个高性能的服务器的呢?你是不是也会有这样的疑问?
tomcat是如何接收网络请求?
如何做到高性能的http协议服务器?

tomcat从8.0往后开始使用了NIO非阻塞io模型,提高了吞吐量,本文的源码是tomcat 9.0.48版本
接收Socket请求org.Apache.tomcat.util.net.Acceptor实现了Runnable接口,在一个单独的线程中以死循环的方式一直进行socket的监听
线程的初始化及启动是在方法org.apache.tomcat.util.net.AbstractEndpoint#startAcceptorThread
有个很重要的属性org.apache.tomcat.util.net.AbstractEndpoint;同时实现了run方法,方法中主要有以下功能:
  • 请求最大连接数限制: 最大为 8*1024;请你注意到达最大连接数后操作系统底层还是会接收客户端连接,但用户层已经不再接收
  • 获取socketChannel
【Apache Tomcat如何高并发处理请求】public void run() {int errorDelay = 0;try {// Loop until we receive a shutdown commandwhile (!stopCalled) {...if (stopCalled) {break;}state = AcceptorState.RUNNING;try {//if we have reached max connections, wait// 如果连接超过了 8*1024,则线程阻塞等待; 是使用org.apache.tomcat.util.threads.LimitLatch类实现了分享锁(内部实现了AbstractQueuedSynchronizer)// 请你注意到达最大连接数后操作系统底层还是会接收客户端连接,但用户层已经不再接收 。endpoint.countUpOrAwaitConnection();// Endpoint might have been paused while waiting for latch// If that is the case, don't accept new connectionsif (endpoint.isPaused()) {continue;}U socket = null;try {// Accept the next incoming connection from the server// socket// 抽象方法,不同的endPoint有不同的实现方法 。NioEndPoint为例,实现方法为serverSock.accept(),这个方法主要看serverSock实例化时如果为阻塞,accept方法为阻塞;反之为立即返回,如果没有socket链接,则为nullsocket = endpoint.serverSocketAccept();} catch (Exception ioe) {// We didn't get a socketendpoint.countDownConnection();if (endpoint.isRunning()) {// Introduce delay if necessaryerrorDelay = handleExceptionWithDelay(errorDelay);// re-throwthrow ioe;} else {break;}}// Successful accept, reset the error delayerrorDelay = 0;// Configure the socketif (!stopCalled && !endpoint.isPaused()) {// setSocketOptions() will hand the socket off to// an Appropriate processor if successful// endPoint类的抽象方法,不同的endPoint有不同的实现 。处理获取到的socketChannel链接,如果该socket链接能正常处理,那么该方法会返回true,否则为falseif (!endpoint.setSocketOptions(socket)) {endpoint.closeSocket(socket);}} else {endpoint.destroySocket(socket);}} catch (Throwable t) {...}}} finally {stopLatch.countDown();}state = AcceptorState.ENDED;}再来看下org.apache.tomcat.util.net.NioEndpoint#setSocketOptions方法的具体实现(NioEndpoint为例)
这个方法中主要做的事:
  • 创建NioChannel
  • 设置socket为非阻塞
  • 将socket添加到Poller的队列中
protected boolean setSocketOptions(SocketChannel socket) {NIOSocketWrapper socketWrapper = null;try {// Allocate channel and wrapper// 优先使用已有的缓存nioChannelNioChannel channel = null;if (nioChannels != null) {channel = nioChannels.pop();}if (channel == null) {SocketBufferHandler bufhandler = new SocketBufferHandler(socketProperties.getAppReadBufSize(),socketProperties.getAppWriteBufSize(),socketProperties.getDirectBuffer());if (isSSLEnabled()) {channel = new SecureNioChannel(bufhandler, this);} else {channel = new NioChannel(bufhandler);}}// 将nioEndpoint与NioChannel进行包装NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);channel.reset(socket, newWrapper);connections.put(socket, newWrapper);socketWrapper = newWrapper;// Set socket properties// Disable blocking, polling will be used// 设置当前链接的socket为非阻塞socket.configureBlocking(false);if (getUnixDomainSocketPath() == null) {socketProperties.setProperties(socket.socket());}socketWrapper.setReadTimeout(getConnectionTimeout());socketWrapper.setWriteTimeout(getConnectionTimeout());socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());// 将包装后的nioChannel与nioEndpoint进行注册,注册到Poller,将对应的socket包装类添加到Poller的队列中,同时唤醒selectorpoller.register(socketWrapper);return true;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);try {log.error(sm.getString("endpoint.socketOptionsError"), t);} catch (Throwable tt) {ExceptionUtils.handleThrowable(tt);}if (socketWrapper == null) {destroySocket(socket);}}// Tell to close the socket if neededreturn false;}


推荐阅读