raksmart0 2011-07-13
近日做性能调优,主要是针对web service,运行于glassfish之上。前期通过修改优化代码,基本搞定一些阻碍性能的问题,主要是代码层次的不合理。之后还是发现性能上不去,而且表现明显不合理:tps只能达到2k,而服务器cpu只停留在10%附近,压力测试的客户端cpu也不高,20%-30%吧。反复thread dump后检查无果,不论是服务器端还是客户端的工作线程都算正常,没有发现线程/锁之类的问题。分析发现主要的症状是服务器端和客户端都压不上去,服务器端工作线程很空闲,客户端则忙于socket通讯及等待服务器返回。
于是开始怀疑问题可能出现在网络通讯上,一边跑压力测试,一边用netstat命令查看socket状态,很快发现问题,有大量多大数k的socket连接出现。感觉不正常,因为应该用的是长连接,按说正常情况socket连接数应该近似等于并发的线程数。测试工具为客户端soapUI,直接连接到运行在glassfish上的web service. soapUI是支持长连接的,glassfish也是支持长连接的。
做了一下验证,只开一个工作线程,跑了几个请求,通过抓包工具发现的确是只建立了一个连接,后面的请求都是跑在同一个socket连接上。试着增加http header Connection: Keep-Alive,发现和默认没有这个参数时表现一致。故意设置为Connection: close,则每次请求都是重新建立连接。因此排除http 是短连接的问题。
继续回头看socket状态,发现出现的多达数k的socket有很多都是处于TIME_WAIT状态,只有少数处于正常的ESTABLISHED状态。TIME_WAIT意味着是服务器端主动要求close socket的,在长连接并且不断有请求的情况下,服务器为什么会如此频繁的关闭连接呢?
试着只开一个压力测试的工作线程,tps大概100+的情况下看服务器端的socket情况,很快发现问题:先是建立一个socket,ESTABLISHED状态,然后大概2s左右重新建立一个新的socket,原有的这个状态转为TIME_WAIT,之后每隔2-3秒左右,都会有上诉的情况出现—-原有连接被放弃,重建新的连接。这样socket就成了1 + n的状态:1个ESTABLISHED + n个TIME_WAIT,一定时间后TIME_WAIT的socket开始逐个消失。
将压力测试的工作线程加到100之后,上述情况开始变的极度激烈,大量TIME_WAIT的socket被建立,数目直接上到1w,2w乃至36000,之后开始偶尔报错说无法连接。
问题基本就定位在这里了,为什么明明建立好了长连接,服务器端确总是会不断的关闭这些长连接导致无数的TIME_WAIT?
试着查找资料,调整参数并反复测试,最终发现和两个参数有关:
1. http.maxConnections
glassfish官网说明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 页面查看相关的系统属性,最后聚焦在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.
从说明上看,应该是max idle connection,和命名maxConnections不大符合,maxConnections感觉像是最多容许开这么多长连接。考虑默认值为5明显应该是max idle connection。
后面的测试验证,就是这个参数非常的致命,在修改为200之后,tps直接 *2。
返回来分析这个参数,默认最多容许有5个空闲长连接。考虑到100个工作线程,正常应该长连接数目也在100附近,考虑每次请求都要先申请一个连接,用完之后再放回,100个工作线程同时操作,很有可能同时将超过5个的连接返还给连接池。如果服务器简单的判断说多于5个连接然后就立即close并释放长连接,那么就会出现一方面连续释放长连接,一方面因为连接数不够不停的创建新的长连接。
换言之,当100个线程并发在连接池中进行申请连接/返还连接的过程中,连接池内的可用连接数是时刻变化的,实际的数目会有大的波动。而默认的最大空闲参数过小(默认才5)使得这个波动有极大的几率突破限制,从而造成连接池进行不必要的释放所谓过多的“空闲”连接。
glassfish中,对于这个参数的修改,非常简单,在jvm参数中增加新的一项”-Dhttp.maxConnections=250″,重启即可。
2. maxKeepAliveRequests
前面的调整,虽然达到了tps * 2的良好效果,但是使用netstat查看socket时,还是发现有非常多的TIME_TIME状态的socket,只是数目没有原来那么直上3w那么夸张,大概稳定在2000附近。
看来还是有其他的原因的,重新回头看当时只开一个线程测试的场景:一个线程连续提交,会出现1个ESTABLISHED + n个TIME_WAIT。
感觉上像是一个长连接上只要跑一段时间或者一定的请求,socket就会被服务器端关闭。修改测试方法,让每次请求之间等待一段时间,降低tps,发现关闭连接的时间间隔大为增加。
后来google到maxKeepAliveRequests这个参数,对于tomcat,apache等服务器都有支持,解释如下:
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也有类似的系统参数可以设置这个maxKeepAliveRequests
-Dcom.sun.enterprise.web.connector.grizzly.maxKeepAliveRequests=-1
使用关键字”glassfishmaxKeepAliveRequests”,发现glassfish还是有支持这个参数的,但是找不到具体设置的方法。后来在glassfish的控制台->Configuration->httpservice->KeepAlive下
发现了一个Max Connections参数,默认值250,解释为”Maximum number of connections in the Keep-Alive mode”,和maxKeepAliveRequests似乎完全不是一回事。但是试着将这个参数修改为2500之后,非常惊讶的发现,见效了!单线程测试长连接释放的速度明显放慢,大体算了一下时间间隔和tpc,无论是之前的默认250还是现在新修改的2500都和测试结果
很合拍。开到100个线程测试,发现原有的2000附近的TIME_WAIT连接被降低到了大概300附近,明显改观。后面发现,可以用下面的参数直接设置:
asadmin set –user admin –passwordfile passwords.txt –port 47348 “server.http-service.keep-alive.max-connections=2500″这里就有点奇怪了,从测试结果来看,这个参数的表现和maxKeepAliveRequests参数的功能是一致的,但是这个参数明明叫做Max Connections,而且旁边的注释”Maximum number of connections in the Keep-Alive mode”也证明了这点。很令人费解,并且这里的Max Connections前面的http.maxConnections有重名嫌疑而作用明显不同。
下面是一个简单的列表,其他情况相同下,分别修改者两个参数前后的对比:
keep-alive.max-connections http.maxConnections test result
2505TPS=650-700TIME-WAIT=32600
250200TPS=1200TIME-WAIT=300
25005TPS=650-700TIME-WAIT=32600
2500 200 TPS=1200 TIME-WAIT=300最终的结果,还是比较理想的,修改了上述两个参数之后,cpu终于压上去了,tps也有了巨大的提升,而且TIME_WAIT的连接也大为减少。但是这两个参数的名称,注释和实际测试中的效果,都有名不副实的感觉,令人困惑。
后续更新:1. 经同事提醒,有新的发现,maxKeepAliveRequests得以确认
http://docs.sun.com/app/docs/doc/820-4343/abefk?a=view
这里是sun的官方资料,其中对MaxConnections参数解释如下:
Max ConnectionsMax 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”参数的确就是通常意义上的”maxKeepAliveRequests”。这里sun的命名不大合适,容易造成误解。refer to:www.blogjava.net/aoxj/archive
本Blog文章除特别声明之外皆为原创文章,欢迎转载,转载请注明: 转载自JSSAY'S BLOG