如何設計與優化高效能的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 failed to respond
:org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(Unknown Source)
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秒的客戶端連線超時
getMethod.setParams(params);
.....
}
老外有一篇文章,很好的描述了類似程式碼的效能隱患:《HttpClient容易忽視的細節——連線關閉》
總述:實現一個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等請求,則可以利用此特新性,使用少量的連線數(IE下一般是2個)更快的下載下來,使得網頁更快的展示出來。
KeepAlive的壞處是:
如果有大量不同的客戶端同時(或瞬間)請求伺服器端,且每一個客戶端的都長期佔用連線(比如:不關閉且ConnectionTimeOut設定過長)或伺服器端也不快速失效連線(KeepAliveTimeout引數設定過大)的話,可能會快速佔滿伺服器連線資源,導致更多的請求被排隊或被拒絕或伺服器down掉。
總結:瀏覽器作為一種HTTP客戶端,充分的、很好的利用了HTTP協議的KeepAlive,讓我們的瀏覽更加快速;而我們自寫的HTTP客戶端程式在KeepAlive特性(伺服器已開啟)下,需要以高資料量訪問一個HTTP介面的時候,每一次請求應當儘快關閉連線釋放資源(重點推薦)或者在同一連線上適當多發幾次請求(不推薦)。
【高效能HTTP應用的策略】
所以,當我們需要一個高效能的HTTP介面型應用時:
1、伺服器端:關閉KeepAlive。
2、伺服器端:最好直接支援HTTP協議(注意用POST,不要GET),而不是任何包裝過的協議,比如:hessian/soap等。
3、伺服器端:在一個請求中,最好設計成:支援多條指令批處理,以節省連線數。
4、伺服器端:對請求的處理應當儘可能的快(如在150ms內)。
5、客戶端:在程式碼中,同一個客戶端例項中全部請求結束後應主動關閉連線(無須事先設定客戶端的ConnectionTimeOut引數)。
6、客戶端:如伺服器未關閉KeepAlive,在同一個客戶端例項中可以適量發出多個請求(總時間應稍小於伺服器KeepAliveTimeout引數)。此方式需要精確操作,不推薦。
最後,在介面設計上,對於一些非同步操作,儘量不要設計成單方面輪詢模式(減少大量無謂請求數),應設計成被呼叫方的非同步結果回撥模式。
【一些優化細節】
在伺服器端,我們一般選用的是Apache+Tomcat/JBoss的組合。關於JBoss的配置及優化可參看JBoss官網。
在客戶端的Java程式碼中,我們最常使用的是HttpClient工具包。
有一些細節要注意:
1、在每一個HttpClient例項發完請求後,(如不再使用)應及時關閉連線。
最簡單的方式是,在HTTP Request Header中傳送(Connection: close),指示伺服器關閉當前連線。
程式碼如下:
method.setRequestHeader("Connection", "close");
2、可以設計為單例模式:無需每次建立HttpClient例項,可多次傳送請求(請求頭設定見第一條)