1. 前言
参考“The HTTP Connector”( http://tomcat.apache.org/tomcat-8.5-doc/config/http.html#Introduction ),描述了当Tomcat接收到新的请求,直到超过Tomcat处理能力时,Tomcat的处理过程,涉及到三个相关的属性:maxThreads、maxConnections、acceptCount。
-
Tomcat的每个请求的持续期间,都需要一个线程。如果当前存在的用来处理请求的线程不足以处理接收到的并发请求,将会创建另外的线程,直接达到配置的maxThreads属性。
-
当连接数量达到了maxConnections属性时,Tomcat服务器会接收(accept),但不会处理更多的请求。额外的请求会被阻塞,直到被处理的连接数量降到maxConnections以下,此时Tomcat服务器会开始接受并处理新的连接。当超过了以上限制时,操作系统可能仍然会基于acceptCount属性值接收新的连接。
-
如果仍然接收到更多的并发请求,这些请求会堆积在Connector创建的服务器socket内,直到达到配置的acceptCount属性。更多的并发请求会接收到连接被拒绝(connection refused)的错误,直到可以有处理的资源可用为止。
maxThreads、maxConnections、acceptCount属性是Tomcat标准HTTP Connector(NIO, NIO2,APR/native)都支持的属性。
maxQueueSize是Executor元素的属性,用于配置处理请求的任务队列长度。
当Tomcat接收的请求超过了以上属性时,都会导致Tomcat无法正常访问,以下分别进行分析。
以下使用的Tomcat版本为8.5.63,使用NIO Connector。
2. Tomcat connector分析
可参考 https://blog.csdn.net/a82514921/article/details/115359632 。
3. maxThreads(处理请求最大线程数)
maxThreads属性指定当前Connector可以创建用于处理请求的最大线程数,决定了可以处理并发请求的最大数量。该属性默认值为200。
若当前Connector关联了某个executor时,会使用指定的executor执行任务,而不会使用Connector的内部线程池,maxThreads属性也会被忽略。
以下首先分析Connector使用内部线程池的情况。
3.1. 创建处理请求的线程池代码分析
Tomcat Connector协议类的父类为org.apache.coyote.AbstractProtocol,该类在tomcat-coyote.jar中,使用了Connector的maxThreads属性,get/setMaxThreads()方法调用了AbstractEndpoint类型endpoint对象的get/setMaxThreads()方法,对成员变量maxThreads进行了操作。
AbstractEndpoint类的子类NioEndpoint、Nio2Endpoint、AprEndpoint中,在startInternal()方法中判断若this.getExecutor()等于null,则执行this.createExecutor()方法,在Nio2Endpoint类的bind()方法中,也执行了该方法。
createExecutor()方法定义在AbstractEndpoint类中,该方法创建用于处理请求的线程池,执行以下操作:
-
设置internalExecutor=true,代表Connector使用内部线程池;
-
创建org.apache.tomcat.util.threads.TaskQueue类的对象,作为任务队列;
-
创建org.apache.tomcat.util.threads.TaskThreadFactory类的对象,该类继承自java.util.concurrent.ThreadFactory类,在创建以上对象时,namePrefix设置为“this.getName() + “-exec-””;
在之前分析过,AbstractEndpoint类的getName()返回值等于AbstractProtocol类的getName()返回值,AbstractProtocol各子类及对应的AbstractEndpoint子类,及Connector使用HTTP协议时(使用HTTPS协议时不同)对应的getName()返回值开头如下:
AbstractProtocol各子类 | AbstractEndpoint各子类 | getName()返回值开头 |
---|---|---|
Http11Protocol | NioEndpoint | http-nio |
Http11NioProtocol | NioEndpoint | http-nio |
Http11Nio2Protocol | Nio2Endpoint | http-nio2 |
Http11AprProtocol | AprEndpoint | http-apr |
- 创建org.apache.tomcat.util.threads.ThreadPoolExecutor类的对象,作为线程池,corePoolSize使用this.getMinSpareThreads(),maximumPoolSize使用this.getMaxThreads(),keepAliveTime使用60秒,workQueue使用上述TaskQueue类的对象,threadFactory使用上述TaskThreadFactory类的对象;
getMinSpareThreads()方法返回值可认为等于minSpareThreads属性值,getMaxThreads()方法返回值可认为等于maxThreads属性值。即以上创建的用于处理请求的线程池,corePoolSize等于minSpareThreads,maximumPoolSize等于maxThreads。
使用jstack或jconsole命令查看Tomcat进程中Connector相关的线程,可以看到如“http-nio-8080-exec-xxx”之类的线程,即以上代码创建的线程。
- 调用以上生成的TaskQueue类的对象的setParent()方法,设置为以上生成的ThreadPoolExecutor类的对象。
3.2. Tomcat线程池与任务队列代码分析
3.2.1. Tomcat线程池创建过程
org.apache.tomcat.util.threads.ThreadPoolExecutor类,继承自java.util.concurrent.ThreadPoolExecutor类,在tomcat-util.jar中。
以上创建org.apache.tomcat.util.threads.ThreadPoolExecutor类对象时,调用的构造方法为“ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)”,在该方法中,会调用父类即java.util.concurrent.ThreadPoolExecutor类的构造方法,传入的RejectedExecutionHandler对象为org.apache.tomcat.util.threads.ThreadPoolExecutor类中的RejectHandler类的对象。
org.apache.tomcat.util.threads.ThreadPoolExecutor$RejectHandler类实现了RejectedExecutionHandler接口,在rejectedExecution()方法中抛出了RejectedExecutionException异常,与java.util.concurrent.ThreadPoolExecutor$AbortPolicy效果相同。
即Tomcat Connector内部线程池,拒绝策略使用RejectedExecutionException,即抛出异常以拒绝任务。
3.2.2. Tomcat任务队列创建过程
TaskQueue类继承自java.util.concurrent.LinkedBlockingQueue类。
以上创建TaskQueue类对象时,调用的构造方法为无参数构造方法,会调用父类LinkedBlockingQueue的无参数构造方法。
LinkedBlockingQueue类的无参数构造方法调用了LinkedBlockingQueue(int capacity)构造方法,传入的容量为Integer.MAX_VALUE,可认为长度是无限。
即Tomcat Connector内部线程池的任务队列长度为无限(Integer.MAX_VALUE)。
3.2.3. Tomcat线程池创建新线程的条件
org.apache.tomcat.util.threads.ThreadPoolExecutor类的execute()方法调用java.util.concurrent.ThreadPoolExecutor类的execute()方法。在java.util.concurrent.ThreadPoolExecutor类中,addWorker()方法用于尝试创建工作线程。
众所周知,使用java.util.concurrent.ThreadPoolExecutor类,若线程数已达到corePoolSize且未超过maximumPoolSize,当队列满时,才会创建新的线程;使用org.apache.tomcat.util.threads.ThreadPoolExecutor类,若线程数已达到corePoolSize且未超过maximumPoolSize,当有请求没有空闲线程能够处理时,就会创建新的线程。以下针对以上创建新线程条件的不同进行对比。
在java.util.concurrent.ThreadPoolExecutor类的execute()方法中,若调用workQueue.offer()方法向工作队列中插入元素返回false,则会调用addWorker()方法尝试创建工作线程。
对于LinkedBlockingQueue类的offer()方法,仅当队列元素满时才会返回false,使java.util.concurrent.ThreadPoolExecutor类的execute()方法创建新的线程。
对于TaskQueue类的offer()方法,当this.parent.getPoolSize()小于this.parent.getMaximumPoolSize()时会返回false,即TaskQueue类对象关联的org.apache.tomcat.util.threads.ThreadPoolExecutor类对象的线程数量小于maximumPoolSize时,就可以创建新的线程。
3.3. Tomcat处理请求线程池已满场景模拟及分析
将Tomcat Connector的maxConnections、acceptCount属性值配置得足够大,将maxThreads设置为1,模拟Tomcat处理请求线程池已满的场景。
<Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" acceptCount="100" maxConnections="100" maxThreads="1"/>
使用curl命令发起请求,访问Tomcat对应端口,通过–trace-time参数指定打印curl每一步执行时的时间。
在多个终端同时执行以下命令,使每次请求将Tomcat处理请求的线程占用指定时间:
curl -v --trace-time http://127.0.0.1:8081/test/sleep_seconds.jsp?sleep_seconds=5
可以观察到以下现象:
-
当Tomcat处理请求的线程不够时,新的请求能够建立连接,但无法立刻读取到服务器返回数据,会被阻塞,现象与Acceptor线程被终止时相同,客户端连接不会被Tomcat终止;
-
当Tomcat处理请求的线程执行完毕变为空闲后,会开始依次执行被阻塞的请求(如在3个终端分别执行以上命令,分别需要等待5、10、15秒读取到服务器返回)。
以上现象的原因如下:
-
Tomcat处理请求时,当连接完成并被accept之后,使用线程池处理对应请求;
-
当线程池数量达到maxThreads导致不能再创建线程后,新到的请求如果完成连接并被accept之后,会被加入到任务队列中;
-
Tomcat Connector默认使用内部线程池,对应的任务队列长度为Integer.MAX_VALUE,可认为队列长度为无限;
-
如果一直没有线程处理结束变为空闲,则处在任务队列中的请求会一直等待,客户端此时无法读取到Tomcat的返回,Tomcat也不会主动将任务队列中的请求结束连接,因此客户端会一直等待;
-
当有线程处理结束变为空闲后,会依次处理任务队列中的请求,此时客户端可以读取到Tomcat的返回,并结束此次请求。
以上sleep_seconds.jsp文件用于使Tomcat的处理请求的线程在执行时等待指定的时间,内容如下,
<%@ page import="java.lang.*"%>
<%
String sleepSeconds = request.getParameter("sleep_seconds");
System.out.println("begin to sleep: " + Thread.currentThread().getName() + " : " + sleepSeconds);
long startTime = System.currentTimeMillis();
Thread.sleep(Integer.parseInt(sleepSeconds) * 1000L);
System.out.println("after sleep: " + Thread.currentThread().getName() + " : " + sleepSeconds);
out.print("start: " + startTime + " end: " + System.currentTimeMillis());
%>
4. maxQueueSize(处理请求队列长度)
Tomcat Connector默认使用内部线程池,当Connector使用命名Executor时,可以通过maxQueueSize属性指定处理请求队列长度。
4.1. Connector使用命名Executor
Tomcat标准HTTP Connector都支持executor属性,该属性用于指定conf/server.xml配置文件中Executor元素。当有配置该属性,且对应的命名executor存在时,connector会使用对应的executor(否则),其他与线程相关的属性会被忽略。否则connector会使用私有的内部线程池。
参考 “The Executor (thread pool)”( http://tomcat.apache.org/tomcat-8.5-doc/config/executor.html ),Executor元素支持以下配置:
- className
指定线程池实现类,需要实现org.apache.catalina.Executor接口,默认值为org.apache.catalina.core.StandardThreadExecutor。
- name
线程池名称,在server.xml可通过该名称进行引用,该属性必须指定,且需要是唯一的。
- daemon
指定线程是否需要为daemon线程,默认值为true。
- namePrefix
指定由线程池创建的每个线程名称的前缀,线程名称格式为namePrefix + threadNumber。
- maxThreads
线程池中活动线程最大数量,默认值为200。该默认值与Connector使用内部线程池时值相同。
- minSpareThreads
永远保持活动的线程最小值,默认值为25。当Connector使用内部线程池时,该默认值为10。
- maxIdleTime
空闲线程被关闭前的时间毫秒数,默认值为60000(1分钟)。
- maxQueueSize
能够进入队列排队的可执行任务最大数量,当超过该值后,Tomcat会拒绝新的任务。默认值为Integer.MAX_VALUE。
4.2. 命名Executor创建线程池代码分析
命名Executor默认使用org.apache.catalina.core.StandardThreadExecutor类,该类在catalina.jar中,startInternal()方法中创建了线程池,执行以下操作:
-
创建TaskQueue对象,作为任务队列,调用构造方法时传入的容量为this.maxQueueSize(创建内部线程池时,无法指定任务队列容量);
-
创建TaskThreadFactory对象,namePrefix使用this.namePrefix,默认值为“tomcat-exec-”,daemon使用this.daemon;
-
创建org.apache.tomcat.util.threads.ThreadPoolExecutor类的对象,作为线程池,与AbstractEndpoint.createExecutor()方法中的操作类似;
-
调用以上生成的TaskQueue类的对象的setParent()方法,设置为以上生成的ThreadPoolExecutor类的对象。
4.3. Tomcat线程池执行任务代码分析
StandardThreadExecutor类的execute()方法的执行步骤如下:
-
调用executor对象,即org.apache.tomcat.util.threads.ThreadPoolExecutor类的execute()方法;
-
若出现RejectedExecutionException异常,且调用((TaskQueue)this.executor.getQueue()).force()方法向线程池任务队列中再次插入当前任务失败时,抛出异常RejectedExecutionException(“Work queue full.”)。
org.apache.tomcat.util.threads.ThreadPoolExecutor类的execute()方法的主要执行步骤如下:
-
调用super即java.util.concurrent.ThreadPoolExecutor类的execute()方法;
-
若出现RejectedExecutionException异常,且调用((TaskQueue)this.executor.getQueue()).force()方法向线程池任务队列中再次插入当前任务失败时,抛出异常RejectedExecutionException。
4.4. Tomcat处理请求的任务队列已满场景模拟
Tomcat maxQueueSize默认值为Integer.MAX_VALUE,大于21亿,超过该值的可能性很小。为了模拟Tomcat maxQueueSize不够的场景,使用以下配置,指定Executor的maxThreads、minSpareThreads与maxQueueSize均为1。
<Executor name="testThreadPool" maxThreads="1" minSpareThreads="1" maxQueueSize="1"/>
<Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" executor="testThreadPool"/>
使用curl命令发起以下请求,使每次请求将Tomcat处理请求的线程占用较长的时间:
curl -v --trace-time http://127.0.0.1:8081/test/sleep_seconds.jsp?sleep_seconds=100
第一个被Tomcat处理的请求会执行以上jsp文件,等待指定的时间后会返回;
由于Executor的maxThreads为1,第二个请求没有线程能够处理,会进入任务队列中,等待第一个请求被处理完毕后会被执行,再等待指定的时间后会返回;
由于Executor的maxQueueSize为1,第三个及之后的请求没有线程能够处理,也无法加入到任务队列中,会被拒绝连接,Tomcat会主动断开连接。
被拒绝连接的curl执行结果如下所示:
13:15:42.342233 * About to connect() to 127.0.0.1 port 8081 (#0)
13:15:42.342342 * Trying 127.0.0.1...
13:15:42.342450 * Connected to 127.0.0.1 (127.0.0.1) port 8081 (#0)
13:15:42.342512 > GET /test/sleep_seconds.jsp?sleep_seconds=100 HTTP/1.1
13:15:42.342512 > User-Agent: curl/7.29.0
13:15:42.342512 > Host: 127.0.0.1:8081
13:15:42.342512 > Accept: */*
13:15:42.342512 >
13:15:42.345888 * Recv failure: Connection reset by peer
13:15:42.345931 * Closing connection 0
curl: (56) Recv failure: Connection reset by peer
查看Tomcat的catalina.out日志,可以看到以下日志,异常信息为“java.util.concurrent.RejectedExecutionException: Work queue full.”,由StandardThreadExecutor.execute()方法抛出,与上述代码一致。
[org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@10dcbc52:org.apache.tomcat.util.net.NioChannel@474893b1:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8081 remote=/127.0.0.1:56713]] for processing
java.util.concurrent.RejectedExecutionException: Work queue full.
at org.apache.catalina.core.StandardThreadExecutor.execute(StandardThreadExecutor.java:172)
at org.apache.tomcat.util.net.AbstractEndpoint.processSocket(AbstractEndpoint.java:1105)
at org.apache.tomcat.util.net.NioEndpoint$Poller.processKey(NioEndpoint.java:896)
at org.apache.tomcat.util.net.NioEndpoint$Poller.run(NioEndpoint.java:872)
at java.lang.Thread.run(Thread.java:748)
5. maxConnections(accept连接最大数量)
maxConnections属性指定了Tomcat服务器在同一时间会accept并处理的连接数量。对于NIO与NIO2 Connector,该默认值为10000,对于APR/nativeConnector,默认值为8192。
5.1. 创建Acceptor线程的代码
创建Acceptor线程的代码,可参考 https://blog.csdn.net/a82514921/article/details/115359632 。
5.2. 对连接进行accept处理代码分析
AbstractEndpoint类的startAcceptorThreads()方法中创建了Acceptor线程,在创建Thread对象时,传入的Runnable对象为this.createAcceptor()方法的返回值,AbstractEndpoint类的createAcceptor()方法为抽象方法,AbstractEndpoint子类的createAcceptor()方法返回值如下:
类名 | createAcceptor()方法返回值 |
---|---|
NioEndpoint | new NioEndpoint.Acceptor() |
Nio2Endpoint | new Nio2Endpoint.Acceptor() |
AprEndpoint | new AprEndpoint.Acceptor() |
NioEndpoint$Acceptor、Nio2Endpoint$Acceptor、AprEndpoint$Acceptor类都继承自AbstractEndpoint$Acceptor类,实现了Runnable接口。以上类的run()方法中,在调用accept()方法接收连接前,都会调用AprEndpoint.this.countUpOrAwaitConnection()方法;在出现异常,或关闭连接前,都会调用this.countDownConnection()方法。
countUpOrAwaitConnection()方法定义在AbstractEndpoint类中,在其中调用了类型为LimitLatch的成员变量connectionLimitLatch的countUpOrAwait()方法;countDownConnection()方法也定义在以上类中,在其中调用了类型为LimitLatch的成员变量connectionLimitLatch的countDown()方法。
AbstractEndpoint类的成员变量connectionLimitLatch在initializeConnectionLatch()方法中,进行实例化,调用LimitLatch类的构建方法,传入的参数为this.getMaxConnections(),即Connector的maxConnections属性值。
参考 http://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/tomcat/util/threads/LimitLatch.html 。
LimitLatch类的构造方法,使用指定的初始限制实例化一个LimitLatch对象,该限制用于指定此锁存器并发获取的最大值。
调用countUpOrAwait()方法时,如果存在可用共享锁存器,则获取一个共享锁存器;如果当前没有可用的共享锁存器,则等待一个共享锁存器。
countDown()方法可以释放一个共享锁存器,使它可被其他线程使用。
根据以上代码可知,AbstractEndpoint子类执行accept操作时的并发控制实现如下:
-
初始化一个最大并发获取数等于maxConnections属性值的共享锁存器;
-
在执行accept操作前,获取一个共享锁存器,若能获取到则立即执行accept操作;若无法获取到则等待获取到共享锁存器后,再执行accept操作;
-
当连接出现异常,或需要关闭连接前,释放当前使用的共享锁存器,使其他线程能够使用。
5.3. ServerSocket不进行accept处理场景分析
在模拟Tomcat最大连接数已满的场景之前,首先分析Java的ServerSocket监听端口但不进行accept处理时的场景(不进行accept处理,与Tomcat最大连接数已满,无法进行accept处理可以认为是相同的场景)。
以下代码,分别对应BIO及NIO的ServerSocket不进行accept处理的场景:
import java.net.InetSocketAddress;
import java.net.ServerSocket;
public class ServerSocketBIONoAccept {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("0.0.0.0", 8080), 1);
while (true) {
Thread.sleep(1000L);
}
}
}
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
public class ServerSocketNIONoAccept {
public static void main(String[] args) throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress("0.0.0.0", 8080), 1);
while (true) {
System.out.println("wait accept");
Socket socket = serverSocket.accept();
System.out.println("after accept");
}
}
}
分别执行以上代码,会使用ServerSocket监听8080端口,但不会进行accept处理。使用telnet命令或其他客户端连接对应的端口,能够建立连接,但服务器不会有返回,客户端会一直阻塞等待服务器返回。与Tomcat的Acceptor线程终止后的现象一样。
5.4. Tomcat最大连接数已满场景模拟
maxConnections属性默认值足够大,Tomcat最大连接数通常情况下并不会被占满。将Tomcat Connector的acceptCount、maxThreads属性值配置得足够大,将maxConnections设置为1,模拟Tomcat最大连接数已满的场景。
<Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" acceptCount="100" maxConnections="1" maxThreads="100"/>
在这种情况下,Tomcat的现象与Tomcat处理请求线程池已满,或Acceptor线程终止时的现象相同,即客户端可以建立连接,但服务器没有返回,客户端会一直阻塞等待服务器返回。
6. acceptCount(connect队列长度)
acceptCount属性用于指定当所有的处理请求的线程都在被使用时,处理接入连接请求的最大队列长度。默认值为100。
当该队列已满时,任意接收到的请求会被拒绝。
6.1. connect队列处理代码分析
AbstractProtocol类的get/setAcceptCount()方法中,调用了AbstractEndpoint类型endpoint对象的get/setAcceptCount()方法,对成员变量acceptCount进行了操作。
AbstractEndpoint子类中对于成员变量acceptCount的使用如下:
- NioEndpoint
在NioEndpoint类的bind()方法中,调用了“this.serverSock.socket().bind(addr, this.getAcceptCount())”方法,即java.net.ServerSocket类的bind(SocketAddress endpoint, int backlog)方法。
- Nio2Endpoint
在Nio2Endpoint类的bind()方法中,调用了“this.serverSock.bind(addr, this.getAcceptCount())”方法,即java.nio.channels.AsynchronousServerSocketChannel类的bind(SocketAddress local, int backlog)方法。
- AprEndpoint
在AprEndpoint类的bind()方法中,调用了“Socket.listen(this.serverSock, this.getAcceptCount())”方法,即org.apache.tomcat.jni.Socket类的native方法listen(long sock, int backlog)。可参考 http://tomcat.apache.org/tomcat-8.5-doc/api/org/apache/tomcat/jni/Socket.html#listen(long,%20int) 。
以上方法均说明backlog参数用于指定Sockect执行bind操作时,接入连接的最大队列长度。
java.net.ServerSocket及java.nio.channels.AsynchronousServerSocketChannel类的bind()方法说明backlog参数的使用取决于具体的实现,有的实现可能会实现该最大长度的限制,有的实现可能会忽略该参数。
java.net.ServerSocket类的bind()方法中,调用了“getImpl().listen(backlog)”方法,即java.net.SocketImpl类的listen(int backlog)方法,backlog参数用于指定接入请求的最大队列长度。当队列满后,若有连接请求达到时,连接会被拒绝。
java.nio.channels.AsynchronousServerSocketChannel及org.apache.tomcat.jni.Socket类对于backlog参数的使用的说明未找到。
6.2. ServerSocket的backlog已满场景分析
6.2.1. 不能及时对套接字进行accept处理
当ServerSocket不能及时对套接字进行accept处理时,以backlog参数值为长度的连接队列可能会被占满。
如以上ServerSocketBIONoAccept、ServerSocketNIONoAccept类,在调用ServerSocket类的bind()方法时,backlog参数为1。且由于没有对套接字进行accept处理,仅能接受一个连接,之后连接队列会被占满。
- 在Linux环境监听
在Linux环境启动以上类后,可以使用“ curl -v http://127.0.0.1:8080 ”或telnet命令访问对应的端口;
前backlog + 1个连接能够正常建立,之后的请求均无法建立连接。
- 在Windows环境监听
在Windows环境启动以上类后,可以使用telnet命令访问对应的端口。
前backlog个连接能够正常建立,之后的请求均无法建立连接。
6.2.2. 能够及时对套接字进行accept处理
当ServerSocket能够及时对套接字进行accept处理时,以backlog参数值为长度的连接队列不会被占满。
如修改以上ServerSocketBIONoAccept、ServerSocketNIONoAccept类,在执行ServerSocket类的bind()方法后,执行accept()方法,当能够及时对套接字进行accept处理时,连接队列不会被占满,可以接受多个连接。
6.3. Tomcat连接队列已满场景模拟
maxConnections属性默认值足够大,能够快速地对连接进行accept操作,连接队列通常情况下并不会被占满。将Tomcat Connector的acceptCount、maxThreads属性值设置为1,将maxConnections配置得足够大,模拟Tomcat连接队列已满的场景。
<Connector port="8081" protocol="org.apache.coyote.http11.Http11NioProtocol" acceptCount="1" maxConnections="1" maxThreads="100"/>
- Linux环境
在Linux环境启动Tomcat,在多个客户端同时使用curl命令或其他方式分别访问上述sleep_seconds.jsp,使当前请求等待较长的时间才处理完毕,例如访问的URL为http://127.0.0.1:8081/test/sleep_seconds.jsp?sleep_seconds=100。
前三个发送到Tomcat服务器的请求会被Tomcat处理,并等待服务器返回。从第四个请求开始连接被拒绝。
- Windows环境
前两个发送到Tomcat服务器的请求会被Tomcat处理,并等待服务器返回。从第三个请求开始连接被拒绝。