本文前半部分結論存在嚴重錯誤,請看最后2015-1-20更新部分。
最近一直在解決線上一個問題,表現是:
Tomcat每到凌晨會有一個高峰,峰值的並發達到了3000以上,最后的結果是Tomcat線程池滿了,日志看很多請求超過了1s。
服務器性能很好,Tomcat版本是7.0.54,配置如下:
<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,而且發現即使峰值過了線程數並不會降下來。
我們首先想到的是:
后端應用的處理瞬間比較慢,“堵住了”導致前端線程數漲了起來。
但是優化一個版本上線后發現雖然漲的情況有所好轉,但是最終線程池還是會達到3000這個最大值。
==================================分割線=========================================
以上是大背景,中間的過程省略,直接跟各位說下目前我得到的結論:
1、首先是為什么線程不釋放的問題?
簡單說下我驗證的Tomcat(7.0.54)線程池大概的工作機制
- Tomcat啟動時如果沒有請求過來,那么線程數(都是指線程池的)為0;
- 一旦有請求,Tomcat會初始化minSapreThreads設置的線程數;
- Tomcat不會主動對線程池進行收縮,除非確定沒有任何請求的時候,Tomcat才會將線程池收縮到minSpareThreads設置的大小;
- Tomcat6之前的版本有一個maxSpareThreads參數,但是在7中已經移除了,所以只要前面哪怕只有一個請求,Tomcat也不會釋放多於空閑的線程。
至於Tomcat為什么移除maxSpareThreads這個參數,我想也是出於性能的考慮,不停的收縮線程池性能肯定不高,而多余的線程處於等待狀態的好處是一有新請求過來立刻可以處理。
- 而且大量的Tomcat線程處於等待狀態不會消耗CPU,但是會消耗一些JVM存儲。
補充:上面標紅的一句有點問題,進一步驗證發現只有使用Keep-Alive(客戶端和服務端都支持)時才是這種表現,如果客戶端沒有使用Keep-Alive那么線程會隨着TCP連接的釋放而回收。
Tomcat中Keep-Alive相關的參數:
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.
2、為什么線程池會滿?
這是我現在糾結的核心。到底是不是應用的性能慢導致的,我現在的結論是有關系,但關鍵是並發。
- Tomcat的線程池的線程數跟你的瞬間並發有關系,比如maxThreads設置為1000,當瞬間並發達到1000那么Tomcat就會起1000個線程來處理,這時候跟你應用的快慢關系不大。
那么是不是並發多少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 is10000
. For APR/native, the default is8192
.……
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這個參數和並發數,當並發數超過這個參數則請求會排隊,這時響應的快慢就看你的程序性能了。
以上的結論都是我個人驗證和總結,如有不對,跪求指正!!!
==========================更新(2015-1-20)===========================
以上的得出結論有嚴重的問題,特此更正下,如果誤導了某些同學十分抱歉。
主要錯誤的結論是:
- Tomcat不會主動對線程池進行收縮,除非確定沒有任何請求的時候,Tomcat才會將線程池收縮到minSpareThreads設置的大小;
- Tomcat6之前的版本有一個maxSpareThreads參數,但是在7中已經移除了,所以只要前面哪怕只有一個請求,Tomcat也不會釋放多於空閑的線程。
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每秒處理的請求數(從Jmeter或LoadRunner來看可以理解為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就釋放掉。
假設首先線程池在高峰時期暴漲到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可以很容易的控制請求的頻率。