tomcat 的最大连接数设置
编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。
背景与目的
为了确保服务不会被过多的 HTTP 长连接压垮,我们需要对 Tomcat 设定最大连接数。超过该连接数的请求将被拒绝,从而将负载引导至其它机器。这既能达到自我保护的目的,也能起到连接数负载均衡的作用。
核心参数探索与实验
初步尝试:MaxKeepAliveRequests
一开始根据故障 TodoList 提供的参数 MaxKeepAliveRequests 进行验证。我们将 Tomcat 配置 server.xml 修改为:
同时,启动客户端模拟 30 个长连接。预期应该只有 10 个连接能保持住,但结果与预期不符:30 个连接都连上了,而且正常。
这让我们怀疑该配置参数是否真正限制了最大连接数。KeepAlive 是在 HTTP/1.1 中定义的,用来保持客户机和服务器的长连接,通过减少建立 TCP Session 的次数来提高性能。常用的配置参数有 {KeepAlive, KeepAliveTimeout, MaxKeepAliveRequests},具体含义如下:
- KeepAlive:决定开启 KeepAlive 支持。
- KeepAliveTimeout:决定一个 KeepAlive 的连接能保持多少时间。Timeout 后就尽快 shutdown 链接,若还有数据必须再建立新的连接。
- MaxKeepAliveRequests:与
KeepAliveTimeout相似,意思是服务多少个请求就 shutdown 连接。
显然,这些参数与我们想要求的“最大连接数限制”不符。
再次尝试:maxConnections
搜索其它配置参数后,发现 maxConnections。根据字面意思觉得就应该是这个了。去验证吧:
最大连接数设置为 10,我们启动 30 个长连接。预期应该是只有 10 个长连接,实际结果却是远超过 10 个。这个现象有点不应该。
最终验证:maxThreads 与 acceptCount
原来还有个参数可以决定连接数的大小:
- maxThreads:Tomcat 启动的最大线程数,即同时处理的任务个数,默认值为 200。
- acceptCount:当 Tomcat 启动的线程数达到最大时,接受排队的请求个数,默认值为 100。
这两个值如何起作用,请看下面三种情况:
- 情况 1:接受一个请求,此时 Tomcat 启动的线程数没有到达
maxThreads,Tomcat 会启动一个线程来处理此请求。 - 情况 2:接受一个请求,此时 Tomcat 启动的线程数已经到达
maxThreads,Tomcat 会把此请求放入等待队列,等待空闲线程。 - 情况 3:接受一个请求,此时 Tomcat 启动的线程数已经到达
maxThreads,等待队列中的请求个数也达到了acceptCount,此时 Tomcat 会直接拒绝此次请求,返回connection refused。
同时加上 maxConnections 配置:
原来 Tomcat 最大连接数取决于 maxConnections 这个值加上 acceptCount 这个值。在连接数达到了 maxConnections 之后,Tomcat 仍会保持住连接,但是不处理,等待其它请求处理完毕之后才会处理这个请求。
源码原理分析
Tomcat 的最大连接数参数是 maxConnections,这个值表示最多可以有多少个 socket 连接到 Tomcat 上。
- BIO 模式:默认最大连接数是它的最大线程数(缺省是 200)。
- NIO 模式:默认是 10000。
- APR 模式:则是 8192(Windows 上则是低于或等于
maxConnections的 1024 的倍数)。 - 不限制:如果设置为
-1则表示不限制。
在 Tomcat 里通过一个计数器来控制最大连接,比如在 Endpoint 的 Acceptor 里大致逻辑如下:
while (running) {
...
//if we have reached max connections, wait
countUpOrAwaitConnection(); //计数 +1,达到最大值则等待
...
// Accept the next incoming connection from the server socket
socket = serverSock.accept();
...
processSocket(socket);
...
countDownConnection(); //计数 -1
closeSocket(socket);
}计数器是通过 LimitLatch 锁来实现的,它内部主要通过一个 java.util.concurrent.locks.AbstractQueuedSynchronizer 的实现来控制。
我们将最大连接数设置为 10,同时启动超过 30 个长连接,然后通过 jstack 可以看到 acceptor 线程阻塞在 countUpOrAwaitConnection 方法上:
"http-nio-8080-Acceptor-0" daemon prio=10 tid=0x00007f9cfc191000 nid=0x1e07 waiting on condition [0x00007f9ca9fde000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076595b688> (a org.apache.tomcat.util.threads.LimitLatch$Sync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:969)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1281)
at org.apache.tomcat.util.threads.LimitLatch.countUpOrAwait(LimitLatch.java:115)
at org.apache.tomcat.util.net.AbstractEndpoint.countUpOrAwaitConnection(AbstractEndpoint.java:755)
at org.apache.tomcat.util.net.NioEndpoint$Acceptor.run(NioEndpoint.java:787)
at java.lang.Thread.run(Thread.java:662)代码层面也解释了这种现象。
调优策略建议
Tomcat 能支持的最大连接数由 maxConnections 加上 acceptCount 来决定。同时 maxThreads 如何设定?以下部分结论引用自:http://duanfei.iteye.com/blog/1894387
一般的服务器操作都包括两方面:1. 计算(主要消耗 CPU),2. 等待(IO、数据库等)。
- 计算密集型:如果我们的操作是纯粹的计算,那么系统响应时间的主要限制就是 CPU 的运算能力。此时
maxThreads应该尽量设的小,降低同一时间内争抢 CPU 的线程个数,可以提高计算效率,提高系统的整体处理能力。 - IO 密集型:如果我们的操作纯粹是 IO 或者数据库,那么响应时间的主要限制就变为等待外部资源。此时
maxThreads应该尽量设的大,这样才能提高同时处理请求的个数,从而提高系统整体的处理能力。此情况下因为 Tomcat 同时处理的请求量会比较大,所以需要关注一下 Tomcat 的虚拟机内存设置和 Linux 的open file限制。
我在测试时遇到一个问题,maxThreads 我设置的比较大比如 3000,当服务的线程数大到一定程度时(一般是 2000 出头),单次请求的响应时间就会急剧的增加。百思不得其解这是为什么,四处寻求答案无果,最后我总结的原因可能是 CPU 在线程切换时消耗的时间随着线程数量的增加越来越大。CPU 把大多数时间都用来在这 2000 多个线程直接切换上了,当然 CPU 就没有时间来处理我们的程序了。
以前一直简单的认为多线程=高效率,其实多线程本身并不能提高 CPU 效率,线程过多反而会降低 CPU 效率。当 CPU 核心数 < 线程数时,CPU 就需要在多个线程直接来回切换,以保证每个线程都会获得 CPU 时间,即通常我们说的并发执行。所以 maxThreads 的配置绝对不是越大越好。
现实应用中,我们的操作都会包含以上两种类型(计算、等待),所以 maxThreads 的配置并没有一个最优值,一定要根据具体情况来配置。最好的做法是:在不断测试的基础上,不断调整、优化,才能得到最合理的配置。
acceptCount 的配置,我一般是设置的跟 maxThreads 一样大,这个值应该是主要根据应用的访问峰值与平均值来权衡配置的。
- 如果设的较小,可以保证接受的请求较快响应,但是超出的请求可能就直接被拒绝。
- 如果设的较大,可能就会出现大量的请求超时的情况,因为我们系统的处理能力是一定的。
Tomcat 6 的 Connector 配置示例如下:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
maxThreads="800" acceptCount="1000"/>附:Linux 系统连接数查看
以下命令可用于查看系统当前的连接状态及并发数。
1. 查看当前并发访问数
netstat -an | grep ESTABLISHED | wc -l可对比 httpd.conf 中 MaxClients 的数字差距多少。
2. 查看进程数
ps aux | grep httpd | wc -l3. 统计特定进程数
ps -ef | grep httpd | wc -l统计 httpd 进程数,每个请求会启动一个进程,适用于 Apache 服务器。表示 Apache 能够处理 1388 个并发请求,这个值 Apache 可根据负载情况自动调整。
4. 查看特定端口连接数
netstat -nat | grep -i "80" | wc -lnetstat -an 会打印系统当前网络链接状态,而 grep -i "80" 是用来提取与 80 端口有关的连接的,wc -l 进行连接数统计。最终返回的数字就是当前所有 80 端口的请求总数。
5. 查看已建立连接数
netstat -an | grep ESTABLISHED | wc -lnetstat -an 会打印系统当前网络链接状态,而 grep ESTABLISHED 提取出已建立连接的信息,然后 wc -l 统计。最终返回的数字就是当前所有 80 端口的已建立连接的总数。
查看所有建立连接的详细记录:
netstat -nat | grep ESTABLISHED | wc6. 查看 TCP 连接状态
查看 Apache 的并发请求数及其 TCP 连接状态:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'返回结果示例:
LAST_ACK 5
SYN_RECV 30
ESTABLISHED 1597
FIN_WAIT1 5
FIN_WAIT2 504
TIME_WAIT 1057其中的状态含义:
- SYN_RECV:表示正在等待处理的请求数。
- ESTABLISHED:表示正常数据传输状态。
- TIME_WAIT:表示处理完毕,等待超时结束的请求数。
说明:本文基于 Tomcat 6 及早期版本测试撰写。不同 Tomcat 版本(如 8.5/9/10)在连接器(Connector)实现及默认参数上可能存在差异,生产环境配置请以官方文档及实际压测结果为准。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/tomcat-de-zui-da-lian-jie-shu-she-zhi.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。




