近日做性能調(diào)優(yōu),主要是針對web service,運行于glassfish之上。前期通過修改優(yōu)化代碼,基本搞定一些阻礙性能的問題,主要是代碼層次的不合理。
之后還是發(fā)現(xiàn)性能上不去,而且表現(xiàn)明顯不合理:tps只能達到2k,而服務(wù)器cpu只停留在10%附近,壓力測試的客戶端cpu也不高,20%-30%吧。反復(fù)thread dump后檢查無果,不論是服務(wù)器端還是客戶端的工作線程都算正常,沒有發(fā)現(xiàn)線程/鎖之類的問題。分析發(fā)現(xiàn)主要的癥狀是服務(wù)器端和客戶端都壓不上去,服務(wù)器端工作線程很空閑,客戶端則忙于socket通訊及等待服務(wù)器返回。
于是開始懷疑問題可能出現(xiàn)在網(wǎng)絡(luò)通訊上,一邊跑壓力測試,一邊用netstat命令查看socket狀態(tài),很快發(fā)現(xiàn)問題,有大量多大數(shù)k的socket連接出現(xiàn)。感覺不正常,因為應(yīng)該用的是長連接,按說正常情況socket連接數(shù)應(yīng)該近似等于并發(fā)的線程數(shù)。測試工具為客戶端soapUI,直接連接到運行在glassfish上的web service. soapUI是支持長連接的,glassfish也是支持長連接的。
做了一下驗證,只開一個工作線程,跑了幾個請求,通過抓包工具發(fā)現(xiàn)的確是只建立了一個連接,后面的請求都是跑在同一個socket連接上。試著增加http header Connection: Keep-Alive,發(fā)現(xiàn)和默認(rèn)沒有這個參數(shù)時表現(xiàn)一致。故意設(shè)置為Connection: close,則每次請求都是重新建立連接。因此排除http 是短連接的問題。
繼續(xù)回頭看socket狀態(tài),發(fā)現(xiàn)出現(xiàn)的多達數(shù)k的socket有很多都是處于TIME_WAIT狀態(tài),只有少數(shù)處于正常的ESTABLISHED狀態(tài)。TIME_WAIT意味著是服務(wù)器端主動要求close socket的,在長連接并且不斷有請求的情況下,服務(wù)器為什么會如此頻繁的關(guān)閉連接呢?
試著只開一個壓力測試的工作線程,tps大概100+的情況下看服務(wù)器端的socket情況,很快發(fā)現(xiàn)問題:先是建立一個socket,ESTABLISHED狀態(tài),然后大概2s左右重新建立一個新的socket,原有的這個狀態(tài)轉(zhuǎn)為TIME_WAIT,之后每隔2-3秒左右,都會有上訴的情況出現(xiàn)----原有連接被放棄,重建新的連接。這樣socket就成了1 + n的狀態(tài):1個ESTABLISHED + n個TIME_WAIT,一定時間后TIME_WAIT的socket開始逐個消失。
將壓力測試的工作線程加到100之后,上述情況開始變的極度激烈,大量TIME_WAIT的socket被建立,數(shù)目直接上到1w,2w乃至36000,之后開始偶爾報錯說無法連接。
問題基本就定位在這里了,為什么明明建立好了長連接,服務(wù)器端確總是會不斷的關(guān)閉這些長連接導(dǎo)致無數(shù)的TIME_WAIT?
試著查找資料,調(diào)整參數(shù)并反復(fù)測試,最終發(fā)現(xiàn)和兩個參數(shù)有關(guān):
1. http.maxConnections
glassfish官網(wǎng)說明
https://metro.dev.java.net/guide/HTTP_Persistent_Connections__keep_alive_.html
HTTP keep-alive behavior can be controlled by the http.keepAlive (default: true) and http.maxConnections (default: 5) system properties. For more information, see Networking Properties
進入http://java.sun.com/j2se/1.5.0/docs/guide/net/properties.html 頁面查看相關(guān)的系統(tǒng)屬性,最后聚焦在http.maxConnections :
http.maxConnections (default: 5)
If HTTP keep-alive is enabled, this value is the number of idle connections that will be simultaneously kept alive, per-destination.
從說明上看,應(yīng)該是max idle connection,和命名maxConnections不大符合,maxConnections感覺像是最多容許開這么多長連接。考慮默認(rèn)值為5明顯應(yīng)該是max idle connection。
后面的測試驗證,就是這個參數(shù)非常的致命,在修改為200之后,tps直接 *2。
返回來分析這個參數(shù),默認(rèn)最多容許有5個空閑長連接。考慮到100個工作線程,正常應(yīng)該長連接數(shù)目也在100附近,考慮每次請求都要先申請一個連接,用完之后再放回,100個工作線程同時操作,很有可能同時將超過5個的連接返還給連接池。如果服務(wù)器簡單的判斷說多于5個連接然后就立即close并釋放長連接,那么就會出現(xiàn)一方面連續(xù)釋放長連接,一方面因為連接數(shù)不夠不停的創(chuàng)建新的長連接。
換言之,當(dāng)100個線程并發(fā)在連接池中進行申請連接/返還連接的過程中,連接池內(nèi)的可用連接數(shù)是時刻變化的,實際的數(shù)目會有大的波動。而默認(rèn)的最大空閑參數(shù)過小(默認(rèn)才5)使得這個波動有極大的幾率突破限制,從而造成連接池進行不必要的釋放所謂過多的“空閑”連接。
glassfish中,對于這個參數(shù)的修改,非常簡單,在jvm參數(shù)中增加新的一項"-Dhttp.maxConnections=250",重啟即可。
2. maxKeepAliveRequests
前面的調(diào)整,雖然達到了tps * 2的良好效果,但是使用netstat查看socket時,還是發(fā)現(xiàn)有非常多的TIME_TIME狀態(tài)的socket,只是數(shù)目沒有原來那么直上3w那么夸張,大概穩(wěn)定在2000附近。
看來還是有其他的原因的,重新回頭看當(dāng)時只開一個線程測試的場景:一個線程連續(xù)提交,會出現(xiàn)1個ESTABLISHED + n個TIME_WAIT。
感覺上像是一個長連接上只要跑一段時間或者一定的請求,socket就會被服務(wù)器端關(guān)閉。修改測試方法,讓每次請求之間等待一段時間,降低tps,發(fā)現(xiàn)關(guān)閉連接的時間間隔大為增加。
后來google到maxKeepAliveRequests這個參數(shù),對于tomcat,apache等服務(wù)器都有支持,解釋如下:
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.
隨即google到grizzly也有類似的系統(tǒng)參數(shù)可以設(shè)置這個maxKeepAliveRequests
-Dcom.sun.enterprise.web.connector.grizzly.maxKeepAliveRequests=-1
使用關(guān)鍵字"glassfish maxKeepAliveRequests",發(fā)現(xiàn)glassfish還是有支持這個參數(shù)的,但是找不到具體設(shè)置的方法。后來在glassfish的控制臺-> Configuration -> http service -> Keep Alive 下
發(fā)現(xiàn)了一個Max Connections參數(shù),默認(rèn)值250,解釋為"Maximum number of connections in the Keep-Alive mode",和maxKeepAliveRequests似乎完全不是一回事。
但是試著將這個參數(shù)修改為2500之后,非常驚訝的發(fā)現(xiàn),見效了!單線程測試長連接釋放的速度明顯放慢,大體算了一下時間間隔和tpc,無論是之前的默認(rèn)250還是現(xiàn)在新修改的2500都和測試結(jié)果
很合拍。開到100個線程測試,發(fā)現(xiàn)原有的2000附近的TIME_WAIT連接被降低到了大概300附近,明顯改觀。后面發(fā)現(xiàn),可以用下面的參數(shù)直接設(shè)置:
asadmin set --user admin --passwordfile passwords.txt --port 47348 "server.http-service.keep-alive.max-connections=2500"
這里就有點奇怪了,從測試結(jié)果來看,這個參數(shù)的表現(xiàn)和maxKeepAliveRequests參數(shù)的功能是一致的,但是這個參數(shù)明明叫做Max Connections,而且旁邊的注釋"Maximum number of connections in the Keep-Alive mode"也證明了這點。很令人費解,并且這里的Max Connections前面的http.maxConnections有重名嫌疑而作用明顯不同。
下面是一個簡單的列表,其他情況相同下,分別修改者兩個參數(shù)前后的對比:
keep-alive.max-connections http.maxConnections test result
250
5 TPS=650-700 TIME-WAIT=32600
250
200 TPS=1200 TIME-WAIT=300
2500
5 TPS=650-700 TIME-WAIT=32600
2500 200
TPS=1200 TIME-WAIT=300
最終的結(jié)果,還是比較理想的,修改了上述兩個參數(shù)之后,cpu終于壓上去了,tps也有了巨大的提升,而且TIME_WAIT的連接也大為減少。但是這兩個參數(shù)的名稱,注釋和實際測試中的效果,都有名不副實的感覺,令人困惑。
后續(xù)更新:
1. 經(jīng)同事提醒,有新的發(fā)現(xiàn),maxKeepAliveRequests得以確認(rèn)
http://docs.sun.com/app/docs/doc/820-4343/abefk?a=view
這里是sun的官方資料,其中對Max Connections 參數(shù)解釋如下:
Max Connections
Max Connections controls the number of requests that a particular client can make over a keep-alive connection. The range is any positive integer, and the default is 256.
Adjust this value based on the number of requests a typical client makes in your application. For best performance specify quite a large number, allowing clients to make many requests.
因此可見這個"max connections"參數(shù)的確就是通常意義上的"maxKeepAliveRequests"。這里sun的命名不大合適,容易造成誤解。