文章目录
- 背景
- 原理
- 压测
- 5个并发压测
- 10个并发压测
- 60个并发压测
- 61个并发压测
背景
为了搞懂Tomat的连接+线程模型,搞清楚每个配置参数的作用,实际压测看一下是否与预期一致。
Tomcat配置如下:
server:
# tomcat配置
tomcat:
# 允许最大连接数,默认8192,当达到临界值时,系统会基于accept-count继续接受连接
max-connections: 50
# 当所有线程都在使用时,建立连接的请求的等待队列长度,默认100
accept-count: 10
# 连接器在关闭空闲连接之前等待的毫秒数,默认 20000 20s
connection-timeout: 20s
uri-encoding: UTF-8
threads:
# 线程池的最小工作线程数,默认10
min-spare: 5
# 线程池的最大线程数,默认200
max: 5
mbeanregistry:
# 是否启用Tomcat的MBean注册表
enabled: true
测试接口伪代码:
controller {
/get
String get(){
Sleep(10s);
}
}
原理
Tomcat的NioEndpoint实现了I/O多路复用模型。
Java的多路复用器的使用:
- 创建一个Selector,在其上注册感兴趣的事件,然后调用select方法,等待感兴趣的事情发生
- 感兴趣的事情发生了,比如可读了,就创建一个新的线程从Channel中读数据
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.TaskQueue
AbstractEndpoint.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 this if (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
# 当所有线程都在使用时,建立连接的请求的等待队列长度,默认100
accept-count: 10
# 连接器在关闭空闲连接之前等待的毫秒数,默认 20000 20s
connection-timeout: 20s
uri-encoding: UTF-8
threads:
# 线程池的最小工作线程数,默认10
min-spare: 5
# 线程池的最大线程数,默认200
max: 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
5个并发压测
期望:因为Tomcat配置的线程数刚好是5,所以RT都是10s左右
实际:RT都是10s左右
10个并发压测
期望:因为Tomcat配置的线程数刚好是5,会有5个连接排队等10s,所以RT应该是5个10s左右,5个20s左右。平均值为15s左右
实际:平均RT是15s左右
60个并发压测
期望:因为Tomcat配置maxConnections+acceptCount = 50 + 10 =60,应该刚好不会出现Connection Refused
错误,但是有大量RT很长。
实际:没有错误。
当前连接数
61个并发压测
期望:因为Tomcat配置maxConnections+acceptCount = 50 + 10 =60,会出现一个Connection Refused
错误,有大量RT很长。
实际:有一个错误。
参考:
- https://www.datadoghq.com/blog/tomcat-architecture-and-performance/
- https://www.cnblogs.com/sandyflower/p/15495419.html