1. 程式人生 > >Tomcat執行緒數

Tomcat執行緒數

Tomcat每到凌晨會有一個高峰,峰值的併發達到了3000以上,最後的結果是Tomcat執行緒池滿了,日誌看很多請求超過了1s。

 <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
        maxThreads="3000" minSpareThreads="800"/>

    <Connector executor="tomcatThreadPool" port="8084" protocol="org.apache.coyote.http11.Http11AprProtocol"
               connectionTimeout="60000"
               keepAliveTimeout="30000"
               maxKeepAliveRequests="8000"
               maxHttpHeaderSize="8192"
               URIEncoding="UTF-8"
               enableLookups="false"
               acceptCount="1000"
               disableUploadTimeout="true"
               redirectPort="8443" />

事後thread dump看其實真正處於RUNNABLE狀態的執行緒很少,絕大部分執行緒都處於TIMED_WAITING狀態:

於是大夥都開始糾結為什麼執行緒會漲到3000,而且發現即使峰值過了執行緒數並不會降下來。

大量的Tomcat執行緒處於等待狀態不會消耗CPU,但是會消耗一些JVM儲存。

maxKeepAliveRequests:

The maximum number of HTTP requests which can be pipelined until the connection is closed by the server. Setting this attribute to 1 will disable HTTP/1.0 keep-alive, as well as HTTP/1.1 keep-alive and pipelining. Setting this to -1 will allow an unlimited amount of pipelined or keep-alive HTTP requests. If not specified, this attribute is set to 100.

keepAliveTimeout:

The number of milliseconds this Connector will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the connectionTimeout attribute. Use a value of -1 to indicate no (i.e. infinite) timeout. 

那麼是不是併發多少Tomcat就會起多少個執行緒呢?這裡還跟Tomcat的這幾個引數設定有關係,看官方的解釋是最靠譜的:

maxThreads:

The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool.

maxConnections:

The maximum number of connections that the server will accept and process at any given time. When this number has been reached, the server will accept, but not process, one further connection. This additional connection be blocked until the number of connections being processed falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections based on the acceptCount setting. The default value varies by connector type. For BIO the default is the value of maxThreads unless an Executor is used in which case the default will be the value of maxThreads from the executor. For NIO the default is 10000. For APR/native, the default is 8192.

……

acceptCount:

The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 100.

minSpareThreads:

The minimum number of threads always kept running. If not specified, the default of 10 is used.

我簡單理解就是:

maxThreads:Tomcat執行緒池最多能起的執行緒數;

maxConnections:Tomcat最多能併發處理的請求(連線);

acceptCount:Tomcat維護最大的對列數;

minSpareThreads:Tomcat初始化的執行緒池大小或者說Tomcat執行緒池最少會有這麼多執行緒。

比較容易弄混的是maxThreads和maxConnections這兩個引數:

maxThreads是指Tomcat執行緒池做多能起的執行緒數,而maxConnections則是Tomcat一瞬間做多能夠處理的併發連線數。比如maxThreads=1000,maxConnections=800,假設某一瞬間的併發時1000,那麼最終Tomcat的執行緒數將會是800,即同時處理800個請求,剩餘200進入佇列“排隊”,如果acceptCount=100,那麼有100個請求會被拒掉。
注意:根據前面所說,只是併發那一瞬間Tomcat會起800個執行緒處理請求,但是穩定後,某一瞬間可能只有很少的執行緒處於RUNNABLE狀態,大部分執行緒是TIMED_WAITING,如果你的應用處理時間夠快的話。所以真正決定Tomcat最大可能達到的執行緒數是maxConnections這個引數和併發數,當併發數超過這個引數則請求會排隊,這時響應的快慢就看你的程式效能了。

  • Tomcat不會主動對執行緒池進行收縮,除非確定沒有任何請求的時候,Tomcat才會將執行緒池收縮到minSpareThreads設定的大小;
  • Tomcat6之前的版本有一個maxSpareThreads引數,但是在7中已經移除了,所以只要前面哪怕只有一個請求,Tomcat也不會釋放多於空閒的執行緒。

Tomcat會停止長時間閒置的執行緒。

 (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每秒處理的請求數(從Jmeter或LoadRunner來看可以理解為TPS)有關係。通過下表可以清晰的看出來執行緒數TPSmaxIdleTime之間的關係:

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就釋放掉。

假設首先執行緒池在高峰時期暴漲到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,maxConnections只是Tomcat做的一個限制。

歡迎斧正!

補充:

使用Jmeter可以很容易的控制請求的頻率。