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