最近使用HttpClient 4.5 使用 CloseableHttpClient 發起鏈接後,使用CloseableHttpResponse 接受返回結果,結果就報錯了,上網查了下,有位stackoverflow的大兄弟說,只要將:html
CloseableHttpClient httpClient = HttpClients.createDefault();
改成:
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).setConnectionManagerShared(true).build();
就能夠整正常執行了,因而,用之,果真不報錯了,可是爲何呢?如下是大兄弟的原文解釋:
I was having a similar error when I came across this thread and this seemed to fix the issue for me. I know this is an old question, but adding thoughts for others for future reference.java
I'm not 100% sure as to why this fix works as the documentation around this is pretty awful. It was a lot of trial and error with what I was testing to get to this solution. From what I canapache
gather though, this fix works because it is then using a shared connection pool in the background, which means that connections remain open for use.編程
關鍵是最後一句話:大概意思是,後臺使用一個共享鏈接池,供剩下打開的鏈接去使用安全
原文地址:https://stackoverflow.com/questions/41744410/executorservice-performing-rest-requests服務器
感謝下這位大兄弟多線程
apache 官方的建議是,建立鏈接池,併爲每個接口URL分配一個線程,去執行,還給出了許多高併發訪問的編碼技巧併發
原文:https://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.htmlsocket
那麼,使用HttpClient 4.5鏈接池的正確姿式是什麼呢?高併發
原做者地址:https://my.oschina.net/xlj44400/blog/711341
HttpClient 是 Apache Jakarta Common 下的子項目,能夠用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,而且它支持 HTTP 協議最新的版本和建議。HttpClient支持的功能以下:
之前是commons-httpclient,後面被Apache HttpComponents取代,目前版本4.5.x,咱們如今用的就是4.5版本
爲何要用Http鏈接池:
一、下降延遲:若是不採用鏈接池,每次鏈接發起Http請求的時候都會從新創建TCP鏈接(經歷3次握手),用完就會關閉鏈接(4次揮手),若是採用鏈接池則減小了這部分時間損耗 二、支持更大的併發:若是不採用鏈接池,每次鏈接都會打開一個端口,在大併發的狀況下系統的端口資源很快就會被用完,致使沒法創建新的鏈接
private static final Charset CHAR_SET = Charset.forName("utf-8"); private static PoolingHttpClientConnectionManager cm; public void init() { cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(50); cm.setDefaultConnectionConfig(ConnectionConfig.custom() .setCharset(CHAR_SET).build()); SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(30000) .setSoReuseAddress(true).build(); cm.setDefaultSocketConfig(socketConfig); // HttpProtocolParams.setContentCharset(httpParams, "UTF-8"); // HttpClientParams.setCookiePolicy(httpParams, "ignoreCookies"); // HttpConnectionParams.setConnectionTimeout(httpParams, 30000); // HttpConnectionParams.setSoTimeout(httpParams, 30000); httpClient = HttpClientBuilder.create().setConnectionManager(cm) .build(); } public CloseableHttpClient getHttpClient() { int timeout=2; RequestConfig config = RequestConfig.custom() .setConnectTimeout(timeout * 1000) //設置鏈接超時時間,單位毫秒 //.setConnectionRequestTimeout(timeout * 1000) //設置從connect Manager獲取Connection 超時時間,單位毫秒 .setSocketTimeout(timeout * 1000).build(); //請求獲取數據的超時時間,單位毫秒 CloseableHttpClient _httpClient = HttpClients.custom() .setConnectionManager(cm).setDefaultRequestConfig(config) .build(); if(cm!=null&&cm.getTotalStats()!=null) { //打印鏈接池的狀態 LOGGER.info("now client pool {}",cm.getTotalStats().toString()); } return _httpClient; } public String post(String url, Map<String, String> params) { HttpPost post = new HttpPost(url); String resp = null; try { if(params != null){ List<NameValuePair> nvps = new ArrayList<NameValuePair>(); for (Map.Entry<String, String> param : params.entrySet()) { nvps.add(new BasicNameValuePair(param.getKey(), param.getValue())); } post.setEntity(new UrlEncodedFormEntity(nvps, CHAR_SET)); } try { HttpResponse response = httpClient.execute(post); InputStream input = response.getEntity().getContent(); resp = IOUtils.toString(input); } catch (ClientProtocolException e) { LOGGER.error(e.getMessage(), e); } catch (IOException e) { LOGGER.error(e.getMessage(), e); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } finally { if (post != null) post.releaseConnection(); } return resp; }
public class HttpConnectionManager { PoolingHttpClientConnectionManager cm = null; public void init() { LayeredConnectionSocketFactory sslsf = null; try { sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create() .register("https", sslsf) .register("http", new PlainConnectionSocketFactory()) .build(); cm =new PoolingHttpClientConnectionManager(socketFactoryRegistry); cm.setMaxTotal(200); cm.setDefaultMaxPerRoute(20); } public CloseableHttpClient getHttpClient() { CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(cm) .build(); /* //若是不採用鏈接池就是這種方式獲取鏈接 CloseableHttpClient httpClient = HttpClients.createDefault(); */ return httpClient; } }
catch (Exception e) { logger.error("ufile send error e:",e); try { if (resEntity != null && resEntity.getContent() != null) { resEntity.getContent().close(); } } catch (IllegalStateException | IOException e1) { logger.error("ufile send error e1:",e1); } finally { if (getMethod!=null) { getMethod.releaseConnection(); } /*if (httpClient!=null) { //鏈接池使用的時候不能關閉鏈接,不然下次使用會拋異常 java.lang.IllegalStateException: Connection pool shut down try { httpClient.close(); } catch (IOException e2) { logger.error("ufile httpclient close error e2:",e2); } }*/ } }
1. 鏈接池中鏈接都是在發起請求的時候創建,而且都是長鏈接 2. HttpResponse input.close();做用就是將用完的鏈接釋放,下次請求能夠複用,這裏特別注意的是,若是不使用in.close();而僅僅使用httpClient.close();結果就是鏈接會被關閉,而且不能被複用,這樣就失去了採用鏈接池的意義。 3. 鏈接池釋放鏈接的時候,並不會直接對TCP鏈接的狀態有任何改變,只是維護了兩個Set,leased和avaliabled,leased表明被佔用的鏈接集合,avaliabled表明可用的鏈接的集合,釋放鏈接的時候僅僅是將鏈接從leased中remove掉了,並把鏈接放到avaliabled集合中
打印的狀態:
INFO c.m.p.u.h.HttpClientUtils[72] - now client pool [leased: 0; pending: 0; available: 0; max: 50]
leased :the number of persistent connections tracked by the connection manager currently being used to execute requests. available :the number idle persistent connections. pending : the number of connection requests being blocked awaiting a free connection. max: the maximum number of allowed persistent connections.
4.5版本中,這兩個參數的設置都抽象到了RequestConfig中,由相應的Builder構建,具體的例子以下:
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://stackoverflow.com/"); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(5000).setConnectionRequestTimeout(1000) .setSocketTimeout(5000).build(); httpGet.setConfig(requestConfig); CloseableHttpResponse response = httpclient.execute(httpGet); System.out.println("獲得的結果:" + response.getStatusLine());//獲得請求結果 HttpEntity entity = response.getEntity();//獲得請求回來的數據
ConnectTimeoutException
ConnectionPoolTimeout
SocketTimeoutException