posts - 495,comments - 227,trackbacks - 0
          http://blog.csdn.net/shootyou/article/details/6615051#comments


          今天解決了一個HttpClient的異常,汗啊,一個HttpClient使用稍有不慎都會是毀滅級別的啊。

          這里有之前因為route配置不當導致服務器異常的一個處理:http://blog.csdn.net/shootyou/article/details/6415248

          里面的HttpConnectionManager實現就是我在這里使用的實現。


          問題表現:

          tomcat后臺日志發現大量異常

          1. org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection  

          時間一長tomcat就無法繼續處理其他請求,從假死變成真死了。

          linux運行:

          1. netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  
          發現CLOSE_WAIT的數量始終在400以上,一直沒降過。


          問題分析:

          一開始我對我的HttpClient使用過程深信不疑,我不認為異常是來自這里。

          所以我開始從TCP的連接狀態入手,猜測可能導致異常的原因。以前經常遇到TIME_WAIT數過大導致的服務器異常,很容易解決,修改下sysctl就ok了。但是這次是CLOSE_WAIT,是完全不同的概念了。

          關于TIME_WAIT和CLOSE_WAIT的區別和異常處理我會單獨起一篇文章詳細說說我的理解。


          簡單來說CLOSE_WAIT數目過大是由于被動關閉連接處理不當導致的。

          我 說一個場景,服務器A會去請求服務器B上面的apache獲取文件資源,正常情況下,如果請求成功,那么在抓取完資源后服務器A會主動發出關閉連接的請 求,這個時候就是主動關閉連接,連接狀態我們可以看到是TIME_WAIT。如果一旦發生異常呢?假設請求的資源服務器B上并不存在,那么這個時候就會由 服務器B發出關閉連接的請求,服務器A就是被動的關閉了連接,如果服務器A被動關閉連接之后自己并沒有釋放連接,那就會造成CLOSE_WAIT的狀態 了。

          所以很明顯,問題還是處在程序里頭。


          先看看我的HttpConnectionManager實現:

          1. public class HttpConnectionManager {   
          2.   
          3.     private static HttpParams httpParams;  
          4.     private static ClientConnectionManager connectionManager;  
          5.   
          6.     /** 
          7.      * 最大連接數 
          8.      */  
          9.     public final static int MAX_TOTAL_CONNECTIONS = 800;  
          10.     /** 
          11.      * 獲取連接的最大等待時間 
          12.      */  
          13.     public final static int WAIT_TIMEOUT = 60000;  
          14.     /** 
          15.      * 每個路由最大連接數 
          16.      */  
          17.     public final static int MAX_ROUTE_CONNECTIONS = 400;  
          18.     /** 
          19.      * 連接超時時間 
          20.      */  
          21.     public final static int CONNECT_TIMEOUT = 10000;  
          22.     /** 
          23.      * 讀取超時時間 
          24.      */  
          25.     public final static int READ_TIMEOUT = 10000;  
          26.   
          27.     static {  
          28.         httpParams = new BasicHttpParams();  
          29.         // 設置最大連接數  
          30.         ConnManagerParams.setMaxTotalConnections(httpParams, MAX_TOTAL_CONNECTIONS);  
          31.         // 設置獲取連接的最大等待時間  
          32.         ConnManagerParams.setTimeout(httpParams, WAIT_TIMEOUT);  
          33.         // 設置每個路由最大連接數  
          34.         ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);  
          35.         ConnManagerParams.setMaxConnectionsPerRoute(httpParams,connPerRoute);  
          36.         // 設置連接超時時間  
          37.         HttpConnectionParams.setConnectionTimeout(httpParams, CONNECT_TIMEOUT);  
          38.         // 設置讀取超時時間  
          39.         HttpConnectionParams.setSoTimeout(httpParams, READ_TIMEOUT);  
          40.   
          41.         SchemeRegistry registry = new SchemeRegistry();  
          42.         registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));  
          43.         registry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));  
          44.   
          45.         connectionManager = new ThreadSafeClientConnManager(httpParams, registry);  
          46.     }  
          47.   
          48.     public static HttpClient getHttpClient() {  
          49.         return new DefaultHttpClient(connectionManager, httpParams);  
          50.     }  
          51.   
          52. }  


          看到沒MAX_ROUTE_CONNECTIONS 正好是400,跟CLOSE_WAIT非常接近啊,難道是巧合?繼續往下看。

          然后看看調用它的代碼是什么樣的:
          1. public static String readNet (String urlPath)  
          2.     {  
          3.         StringBuffer sb = new StringBuffer ();  
          4.         HttpClient client = null;  
          5.         InputStream in = null;  
          6.         InputStreamReader isr = null;  
          7.         try  
          8.         {  
          9.             client = HttpConnectionManager.getHttpClient();  
          10.             HttpGet get = new HttpGet();  
          11.             get.setURI(new URI(urlPath));  
          12.             HttpResponse response = client.execute(get);  
          13.             if (response.getStatusLine ().getStatusCode () != 200) {  
          14.                 return null;  
          15.             }  
          16.             HttpEntity entity =response.getEntity();  
          17.               
          18.             if( entity != null ){  
          19.                 in = entity.getContent();  
          20.                 .....  
          21.             }  
          22.             return sb.toString ();  
          23.               
          24.         }  
          25.         catch (Exception e)  
          26.         {  
          27.             e.printStackTrace ();  
          28.             return null;  
          29.         }  
          30.         finally  
          31.         {  
          32.             if (isr != null){  
          33.                 try  
          34.                 {  
          35.                     isr.close ();  
          36.                 }  
          37.                 catch (IOException e)  
          38.                 {  
          39.                     e.printStackTrace ();  
          40.                 }  
          41.             }  
          42.             if (in != null){  
          43.                 try  
          44.                 {  
          45.                     <span style="color:#ff0000;">in.close ();</span>  
          46.                 }  
          47.                 catch (IOException e)  
          48.                 {  
          49.                     e.printStackTrace ();  
          50.                 }  
          51.             }  
          52.         }  
          53.     }  

          很簡單,就是個遠程讀取中文頁面的方法。值得注意的是這一段代碼是后來某某同學加上去的,看上去沒啥問題,是用于非200狀態的異常處理:
          1. if (response.getStatusLine ().getStatusCode () != 200) {  
          2.                 return null;  
          3.             }  

          代碼本身沒有問題,但是問題是放錯了位置。如果這么寫的話就沒問題:
          1. client = HttpConnectionManager.getHttpClient();  
          2.             HttpGet get = new HttpGet();  
          3.             get.setURI(new URI(urlPath));  
          4.             HttpResponse response = client.execute(get);  
          5.               
          6.             HttpEntity entity =response.getEntity();  
          7.               
          8.             if( entity != null ){  
          9.                 in = entity.getContent();  
          10.             ..........  
          11.             }  
          12.               
          13.             if (response.getStatusLine ().getStatusCode () != 200) {  
          14.                 return null;  
          15.             }  
          16.             return sb.toString ();  
          看出毛病了吧。在這篇入門(HttpClient4.X 升級 入門 + http連接池使用) 里頭我提到了HttpClient4使用我們常用的InputStream.close()來確認連接關閉,前面那種寫法InputStream in 根本就不會被賦值,意味著一旦出現非200的連接,這個連接將永遠僵死在連接池里頭,太恐怖了。。。所以我們看到CLOST_WAIT數目為400,因為 對一個路由的連接已經完全被僵死連接占滿了。。。

          其實上面那段代碼還有一個沒處理好的地方,異常處理不夠嚴謹,所以最后我把代碼改成了這樣:

          1. public static String readNet (String urlPath)  
          2.     {  
          3.         StringBuffer sb = new StringBuffer ();  
          4.         HttpClient client = null;  
          5.         InputStream in = null;  
          6.         InputStreamReader isr = null;  
          7.         HttpGet get = new HttpGet();  
          8.         try  
          9.         {  
          10.             client = HttpConnectionManager.getHttpClient();  
          11.             get.setURI(new URI(urlPath));  
          12.             HttpResponse response = client.execute(get);  
          13.             if (response.getStatusLine ().getStatusCode () != 200) {  
          14.                 get.abort();  
          15.                 return null;  
          16.             }  
          17.             HttpEntity entity =response.getEntity();  
          18.               
          19.             if( entity != null ){  
          20.                 in = entity.getContent();  
          21.                 ......  
          22.             }  
          23.             return sb.toString ();  
          24.               
          25.         }  
          26.         catch (Exception e)  
          27.         {  
          28.             get.abort();  
          29.             e.printStackTrace ();  
          30.             return null;  
          31.         }  
          32.         finally  
          33.         {  
          34.             if (isr != null){  
          35.                 try  
          36.                 {  
          37.                     isr.close ();  
          38.                 }  
          39.                 catch (IOException e)  
          40.                 {  
          41.                     e.printStackTrace ();  
          42.                 }  
          43.             }  
          44.             if (in != null){  
          45.                 try  
          46.                 {  
          47.                     in.close ();  
          48.                 }  
          49.                 catch (IOException e)  
          50.                 {  
          51.                     e.printStackTrace ();  
          52.                 }  
          53.             }  
          54.         }  
          55.     }  

          顯示調用HttpGet的abort,這樣就會直接中止這次連接,我們在遇到異常的時候應該顯示調用,因為誰能保證異常是在InputStream in賦值之后才拋出的呢。


          好了 ,分析完畢,明天準備總結下CLOSE_WAIT和TIME_WAIT的區別。

          posted on 2012-07-16 12:02 SIMONE 閱讀(5156) 評論(0)  編輯  收藏 所屬分類: JAVA
          主站蜘蛛池模板: 宝兴县| 许昌市| 汝南县| 康马县| 宝兴县| 永靖县| 临西县| 东丰县| 东乌珠穆沁旗| 额敏县| 孟村| 南丹县| 余庆县| 宁德市| 玉田县| 泾源县| 堆龙德庆县| 米林县| 明水县| 宁强县| 昆山市| 民权县| 凤山市| 镶黄旗| 玛沁县| 蒲江县| 金塔县| 襄城县| 鹤峰县| 澄迈县| 玛曲县| 舟曲县| 靖边县| 合川市| 泸州市| 寿宁县| 阜新市| 镇原县| 株洲县| 双鸭山市| 云霄县|