为了搞懂Tomat的连接+线程模型,搞清楚每个配置参数的作用,实际压测看一下是否与预期一致。
Tomcat配置如下:
server:# tomcat配置tomcat:# 允许最大连接数,默认8192,当达到临界值时,系统会基于accept-count继续接受连接max-connections: 50# 当所有线程都在使用时,建立连接的请求的等待队列长度,默认100accept-count: 10# 连接器在关闭空闲连接之前等待的毫秒数,默认 20000 20sconnection-timeout: 20suri-encoding: UTF-8threads:# 线程池的最小工作线程数,默认10min-spare: 5# 线程池的最大线程数,默认200max: 5mbeanregistry:# 是否启用Tomcat的MBean注册表enabled: true
测试接口伪代码:
controller {/getString get(){Sleep(10s);}
}
Tomcat的NioEndpoint实现了I/O多路复用模型。
Java的多路复用器的使用:
NioEndpoint包含LimitLatch、Acceptor、Poller、SocketProcessor和Executor共5个组件。

此处来自:https://www.jianshu.com/p/eb1711261986
线程模型Tomcat源码如下:
org.apache.tomcat.util.net.NioEndPoint.startInternal
- org.apache.tomcat.util.net.AbstractEndpoint.createExecutor- org.apache.tomcat.util.threads.ThreadPoolExecutor- org.apache.tomcat.util.threads.TaskQueueAbstractEndpoint.java
public void createExecutor() {internalExecutor = true;TaskQueue taskqueue = new TaskQueue();// http-nio-8080-exec-TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());// 这里的ThreadPoolExecutor是org.apache.tomcat.util.threads包下的。executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);taskqueue.setParent( (ThreadPoolExecutor) executor);
}
Tomcat通过继承JDK的ThreadPoolExecutor实现自己的ThreadPoolExecutor,记录
已提交但尚未完成的任务数submittedCountTomcat自定义TaskQueue取代JDK中几种队列,配合上面的
submittedCount,实现JDK线程池增强,实现新增线程池到Coresize -> maxSize -> queue.
server.tomcat.threads.max和server.tomcat.threads.min-spare配置线程池。
maxConnections:最大连接数
这个参数是指tomcat能够接受的最大连接数。当Tomcat接收的连接数达到maxConnections时,Acceptor线程不会读取accept队列中的连接;这时accept队列中的线程会一直阻塞着,直到Tomcat接收的连接数小于maxConnections。如果设置为-1,则连接数不受限制。
默认值与连接器使用的协议有关:NIO的默认值是10000?8192,APR/native的默认值是8192,而BIO的默认值为maxThreads(如果配置了Executor,则默认值是Executor的maxThreads)。
(注意:在windows下,APR/native的maxConnections值会自动调整为设置值以下最大的1024的整数倍;如设置为2000,则最大值实际是1024。)
当连接数达到最大值maxConnections后,系统会继续接收连接,但不会超过acceptCount的值.即Tomcat所能处理的最大连接数为
maxConnections + acceptCount
- serverSock.socket().bind(addr,getAcceptCount());
- LimitLatch作用也是对最大连接数的限制
org.apache.tomcat.util.net.AbstractEndpoint private int maxConnections = 8*1024; public void setMaxConnections(int maxCon) {this.maxConnections = maxCon;LimitLatch latch = this.connectionLimitLatch;if (latch != null) {// Update the latch that enforces thisif (maxCon == -1) {releaseConnectionLatch();} else {latch.setLimit(maxCon);}} else if (maxCon > 0) {initializeConnectionLatch();} }
再来个好理解的图:

Tomcat同时可以处理的连接数目是maxConnections,但服务器中可以同时接收的连接数为maxConnections+acceptCount 。acceptCount的设置,与应用在连接过高情况下希望做出什么反应有关系。如果设置过大,后面进入的请求等待时间会很长;如果设置过小,后面进入的请求立马返回connection refused。
总结下总体流程:
启动时,Tomcat 将根据设置的值minSpareThreads创建初始线程,并根据需要增加线程数,直到maxThreads。 如果达到最大线程数,并且所有线程都忙,则服务器将只继续接受一定数量的并发连接maxConnections。一旦达到该限制,新连接将被放入队列 ( acceptCount) 中以等待下一个可用线程。当连接数达到maxConnections并且队列(acceptCount)已满时,任何额外的传入客户端将开始接收Connection Refused错误。
如果您的服务器开始生成这些错误,您将需要调整连接器的线程池容量以更好地适应传入请求的数量。
但是,请注意,如果您将最大线程数和最大队列长度 ( acceptCount) 的值设置得太高,则服务器流量的增加可能会快速消耗过多的服务器资源,因为它会填满队列并用完可用的线程数。
Tomcat配置如下:
压测的接口Sleep(10s)
server:# tomcat配置tomcat:# 允许最大连接数,默认8192,当达到临界值时,系统会基于accept-count继续接受连接max-connections: 50# 当所有线程都在使用时,建立连接的请求的等待队列长度,默认100accept-count: 10# 连接器在关闭空闲连接之前等待的毫秒数,默认 20000 20sconnection-timeout: 20suri-encoding: UTF-8threads:# 线程池的最小工作线程数,默认10min-spare: 5# 线程池的最大线程数,默认200max: 5
应关注的metrics
| 指标 | 定义 |
|---|---|
| tomcat.threads.busy | tomcat 繁忙线程 |
| tomcat.threads.current | tomcat 当前线程数(包括守护线程) |
| tomcat.threads.config.max | tomcat 配置的线程最大数 |
| tomcat.connections.current | 这个默认没有,补上。 |
| tomcat.connections.max | 这个默认没有,补上。 |
connectionCount
org.apache.tomcat.util.net.AbstractEndpoint
- getConnectionCount

相关代码:https://gitee.com/lakernote/easy-admin
期望:因为Tomcat配置的线程数刚好是5,所以RT都是10s左右
实际:RT都是10s左右

期望:因为Tomcat配置的线程数刚好是5,会有5个连接排队等10s,所以RT应该是5个10s左右,5个20s左右。平均值为15s左右
实际:平均RT是15s左右

期望:因为Tomcat配置maxConnections+acceptCount = 50 + 10 =60,应该刚好不会出现Connection Refused错误,但是有大量RT很长。
实际:没有错误。


当前连接数

期望:因为Tomcat配置maxConnections+acceptCount = 50 + 10 =60,会出现一个Connection Refused错误,有大量RT很长。
实际:有一个错误。


参考:
下一篇:MySQL——进阶