随着微服务的流行,服务之间的http调用越来越多,遇到的问题也比较多,写这边文章的目的也是将自己遇到的坑和解决方案跟大家分享
一、为什么要用Http连接池
1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗,别小看这几次握手,本人经过测试发现,基本上3倍的时间延迟
2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接
二、代码
1、PoolingHttpClientFactory.java连接池管理类,支持http与https协议
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; public class PoolingHttpClientFactory { private int timeOut = 30000; private int maxConnectionTotal = 400; private int maxConnectionPerRoute = 400; private PoolingHttpClientConnectionManager poolConnManager; private Builder builder; private RequestConfig requestConfig; private HttpClientBuilder hcb; private static PoolingHttpClientFactory pooling = new PoolingHttpClientFactory(); private PoolingHttpClientFactory() { poolConnManager = createConnectionManager(); builder = RequestConfig.custom(); if(timeOut != 0){ builder.setConnectionRequestTimeout(timeOut).setConnectTimeout(timeOut).setSocketTimeout(timeOut); } requestConfig = builder.build(); hcb = HttpClients.custom().setConnectionManager(poolConnManager).setDefaultRequestConfig(requestConfig); } public static PoolingHttpClientFactory getInstance() { if (pooling == null) { pooling = new PoolingHttpClientFactory(); } return pooling; } public CloseableHttpClient createHttpClient(){ CloseableHttpClient httpClient = hcb.build(); return httpClient; } public PoolingHttpClientConnectionManager createConnectionManager(){ Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSocketFactory()) .build(); PoolingHttpClientConnectionManager poolConnManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); if(maxConnectionTotal != 0){ poolConnManager.setMaxTotal(maxConnectionTotal); } if(maxConnectionPerRoute != 0){ poolConnManager.setDefaultMaxPerRoute(maxConnectionPerRoute); } return poolConnManager; } public void setTimeOut(int timeOut) { this.timeOut = timeOut; } public void setMaxConnectionTotal(int maxConnectionTotal) { this.maxConnectionTotal = maxConnectionTotal; } public void setMaxConnectionPerRoute(int maxConnectionPerRoute) { this.maxConnectionPerRoute = maxConnectionPerRoute; } }
2、连接池消费类:TestClient.java
import com.alibaba.fastjson.JSON; import org.apache.commons.io.IOUtils; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.testng.annotations.Test; import java.io.IOException; import java.io.InputStream; public class TestClient { PoolingHttpClientFactory connManager; public Object get(String path){ CloseableHttpClient httpClient = PoolingHttpClientFactory.getInstance().createHttpClient(); //创建实例对象 HttpGet httpget = new HttpGet(path); String json=null; CloseableHttpResponse response=null; try { response = httpClient.execute(httpget); InputStream in=response.getEntity().getContent(); json= IOUtils.toString(in, "Utf8"); in.close(); } catch (UnsupportedOperationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(response!=null){ try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } return JSON.parse(json); } public Object post(String path){ CloseableHttpClient httpClient = PoolingHttpClientFactory.getInstance().createHttpClient(); //创建实例对象 HttpPost httppost = new HttpPost(path); String json=null; CloseableHttpResponse response=null; try { response = httpClient.execute(httppost); InputStream in=response.getEntity().getContent(); json= IOUtils.toString(in, "Utf8"); in.close(); } catch (UnsupportedOperationException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally { if(response!=null){ try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } return JSON.parse(json); }
三、原理及注意事项
连接池中连接都是在发起请求的时候建立,并且都是长连接
TestClient.java中的in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。
连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中