【转帖】如何设计与优化高性能的HTTP接口型应用
【一个故障引发的话题】
最近,项目中的短信模块收到一个故障日志,要求我协助调查一下:
2010-05-07 09:22:07,221 [?:?] INFO httpclient.HttpMethodDirector - Retrying request
:org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(Unknown Source)
2010-05-07 09:22:07,223 [?:?] INFO httpclient.HttpMethodDirector - I/O exception (org.apache.commons.httpclient.NoHttpResponseException) caught when processing request: The server sms.comm.china.alibaba.com failed to respond
:org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(Unknown Source)
查阅了HttpClient官方的异常说明文档(http://hc.apache.org/httpclient-3.x/exception-handling.html),可以看到以下一段话:
In some circumstances, usually when under heavy load, the web server may be able to receive requests but unable to process them. A lack of sufficient resources like worker threads is a good example. This may cause the server to drop the connection to the client without giving any response. HttpClient throws NoHttpResponseException when it encounters such a condition. In most cases it is safe to retry a method that failed with NoHttpResponseException.
简单的说,此异常是由于服务器端过载而拒绝接受请求(不再响应)所致。
检查了客户端调用的代码(以下代码每隔20秒执行一次,每晚会定期执行约10次)
for (int i = 0; i < 100; i++) {
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod(url);
HttpClientParams params = new HttpClientParams();
params.setConnectionManagerTimeout(120000); //设置120秒的客户端连接超时
params.setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler()); //缺省重试3次
getMethod.setParams(params);
.....
}
老外有一篇文章,很好的描述了类似代码的性能隐患:《HttpClient容易忽视的细节——连接关闭》
1、英文原文:http://www.codeweblog.com/httpclient-s-easy-to-overlook-the-details-the-connection-is-closed/
2、中文翻译:http://www.javaeye.com/topic/234759
总述:实现一个HTTP接口不是件困难的事情,但是如何让这样的HTTP接口在高压力下(短时间内大数据量)也有稳定良好的表现,则不仅仅是HTTP服务器端需要做好设计与优化,而且HTTP客户端方面也同样需要非常谨慎与注意一些代码细节。否则,很有可能因(双方或单方)代码或配置中存在性能隐患,在软硬件环境的配合下就会出现一些“灵异”故障。
【HTTP协议知识】
为便于读者理解后文,先简述一些与HTTP性能密切相关的、又常常被工程师们所不深究的HTTP协议基础知识。
一、什么是HTTP KeepAlive
HTTP KeepAlive是就是通常所称的长连接。KeepAlive即服务器端为同一客户端保持连接一段时间(不立即关闭),以便于更多来自于此客户端的后续请求不断的利用此连接直至连接超时。
在HTTP1.0和HTTP1.1协议中都有对KeepAlive的支持。其中HTTP1.0需要在request header中增加”Connection: keep-alive“ 才能够支持,而HTTP1.1默认支持。
KeepAlive的更多阐述:
1、next request是在完成before request的response被client接收的情况下才发出。因此需要在向client写完before request的response后才能触发。
2、HTTP协议是基于TCP协议的,故服务器端与客户端都有可能关闭连接。KeepAlive只是表明了服务器端面对连接的一种优化策略,而客户端也完全可以主动关闭之(不利用)。
二、KeepAlive的好处与坏处
KeepAlive带来的好处是可以减少HTTP连接的开销,提高性能。比如,同一页面中如有很多内嵌的图片、JS、CSS等请求,则可以利用此特新性,使用很少的连接数(一般是2个)更快的下载下来,使得网页更快的展示出来。
KeepAlive的坏处是:
如果有大量不同的客户端同时(或瞬间)请求服务器端,且每一个客户端的都长期占用连接(比如:不关闭且ConnectionTimeOut设置过长)或服务器端也不快速失效连接(KeepAliveTimeout参数设置过大)的话,可能会快速占满服务器连接资源,导致更多的请求被排队或被拒绝或服务器down掉。
总结:浏览器作为一种HTTP客户端,充分的、很好的利用了HTTP协议的KeepAlive,让我们的浏览更加快速;而我们自写的HTTP客户端程序在KeepAlive特性(服务器已开启)下,需要以高数据量访问一个HTTP接口的时候,每一次请求应当尽快关闭连接释放资源(重点推荐)或者在同一连接上适当多发几次请求(不推荐)。
【高性能应用的策略】
所以,当我们需要一个高性能的HTTP接口型应用时:
1、服务器端:关闭KeepAlive。
2、服务器端:最好直接支持HTTP协议(注意用POST,不要GET),而不是任何包装过的协议,比如:hessian/soap等。
3、服务器端:在一个请求中,最好设计成:支持多条指令批处理,以节省连接数。
4、服务器端:对请求的处理应当尽可能的快(如在150ms内),如不能同步返回执行结果则支持异步回调模式。
5、客户端:在代码中,同一个客户端实例中全部请求结束后应主动关闭连接(无须事先设置客户端的ConnectionTimeOut参数)。
6、客户端:如服务器未关闭KeepAlive,在同一个客户端实例中可以适量发出多个请求(总时间应稍小于服务器KeepAliveTimeout参数)。此方式需要精确操作,不推荐。
【一些优化细节】
在服务器端,我们一般选用的是Apache+Tomcat/JBoss的组合。关于JBoss的配置及优化可参看JBoss官网。
最主要的是关于Apache的优化,推荐阅读两篇文章:
1、Apache性能优化:http://www.aliwo.net:8080/2009/12/apache/
2、Apache中KeepAlive配置的合理使用:http://www.net527.cn/a/caozuoxitong/Linux/5283.html
在客户端的Java代码中,我们最常使用的是HttpClient工具包。
有一些细节要注意:
1、在每一个HttpClient实例发完请求后,(如不再使用)应及时关闭连接。
最简单的方式是,在HTTP Request Header中发送(Connection: close),指示服务器关闭当前连接。
代码如下:
method.setRequestHeader("Connection", "close");
2、可以设计为单例模式:无需每次创建HttpClient实例,可多次发送请求(请求头设置见第一条)