聊下并发和Tomcat线程数(错误更正)

Published: by Creative Commons Licence

之前在博客园写过一篇文章:聊下并发和Tomcat线程数。其中得出的结论是错误,特此更正下,如果误导了某些同学十分抱歉。原文也已同步更新了。

错误的结论

那篇文章有问题的结论是:

  • Tomcat不会主动对线程池进行收缩,除非确定没有任何请求的时候,Tomcat才会将线程池收缩到minSpareThreads设置的大小;
  • Tomcat6之前的版本有一个maxSpareThreads参数,但是在7中已经移除了,所以只要前面哪怕只有一个请求,Tomcat也不会释放多于空闲的线程。

线程数、TPS、maxIdleTime之间的关系

其实Tomcat会停止长时间闲置的线程。Tomcat还有一个参数叫maxIdleTime

(int) The number of milliseconds before an idle thread shutsdown, unless the number of active threads are less or equal to minSpareThreads. Default value is 60000(1 minute)

其实从这个参数解释也能看出来Tomcat会停止闲置了超过一定时间的线程的,这个时间就是maxIdleTime。但我之前的测试中确实没有发现线程释放的现象,这是为什么呢?我发现除了这个参数线程池线程是否释放?释放多少?还跟当前Tomcat每秒处理的请求数(从JmeterLoadRunner来看可以理解为TPS)有关系。通过下表可以清晰的看出来线程数TPS和maxIdleTime之间的关系:

TPS maxIdleTime(ms) Thread Count
10 60,000 600
5 60,000 300
1 60,000 60

依次类推,上表中Thread Count这一列是一个大约数,上下相差几个,但基本符合这样一个规则:

Thread Count = min(max((TPS * maxIdleTime)/1000,minSpareThreads),maxThreads)

当然这个Thread Count不会小于minSpareThreads,这个跟之前的结论还是一样的。我现在大胆猜测下(回头看源码验证下,或者哪位同学知道告诉我下,谢谢):

Tomcat线程池每次从队列头部取线程去处理请求,请求完结束后再放到队列尾部,也就是说前后两次请求处理不会用同一个线程。某个线程闲置超过maxIdleTime就释放掉。

201812241101914.png

假设首先线程池在高峰时期暴涨到1000,高峰过后Tomcat处理一次请求需要1s(从Jmeter看TPS大约就为1),那么在maxIdleTime默认的60s内会用到线程池中60个线程,那么最后理论上线程池会收缩到60(假设minSpareThreads大于60)。另外:这个跟用不用Keep-Alive没关系(之前测试结论是因为没用Keep-Alive导致程序性能下降,TPS降低了很多导致的)

这就是为什么我之前的测试中、还有我们生产环境中线程数只增不减的原因,因为就算峰值过后我们的业务每秒请求次数仍然有100以上,100*60=6000,也就是3000个线程每个线程在被回收之前肯定会被重用。

线程池为什么会满

那么现在有另外一个问题,就是正常情况下为什么每秒100次的请求不会导致线程数暴增呢?也就是说线程暴增到3000的瓶颈到底在哪?这个我之前文章中的结论其实也不是很准确。

  • 真正决定Tomcat最大可能达到的线程数是maxConnections这个参数和并发数,当并发数超过这个参数则请求会排队,这时响应的快慢就看你的程序性能了。

这里没说清楚的是并发的概念,不管什么并发肯定是有一个时间单位的(一般是1s),准确的来讲应该是当时Tomcat处理一个请求的时间内并发数,比如当时Tomcat处理一个请求花费了1s,那么如果这1s过来的请求数达到了3000,那么Tomcat的线程数就会为3000,也就是说瓶颈在于当时Tomcat处理请求的速度。maxConnections只是Tomcat做的一个限制。

欢迎斧正!

补充

使用Jmeter可以很容易控制请求的频率。

201844220947370.png

201844332501077.png