为什么使用HTTP连接池?
随着系统架构风格逐渐向前后端分离架构,微服务架构转变,RestFul风格API的开发与设计,同时SpringMVC也很好的支持了REST风格接口。各个系统之间服务的调用大多采用HTTP+JSON
或HTTPS+JSON
方式。
Connection:keep-alive
使得连接成为长连接。既然HTTP协议支持长连接,那么HTTP连接同样可以使用连接池技术来管理和维护连接建立和销毁。 但是由于每次HTTP连接请求实际上都是在传输层建立的TCP连接,利用的socket进行通信,HTTP连接的保持和关闭实际上都同TCP连接的建立和关闭有关,所有每次HTTP请求都有经过TCP连接的三次握手(建立连接)和四次挥手(释放连接)的过程。所以采用HTTP连接池有以下优势: - 降低了频繁建立HTTP连接的时间开销,减少了TCP连接建立和释放时socket通信服务器端资源的浪费;
- 支持更高的并发量;
常用HttpClient连接池API
本文使用的是目前最新版本的HttpClient4.5.3,所以下文的内容都是基于该版本书写。
- PoolingHttpClientConnectionManager连接池管理实现类
PoolingHttpClientConnectionManager
是一个HttpClient连接池实现类,实现了HttpClientConnectionManager
和ConnPoolControl
接口。类层次结构如下图所示:
构造方法:
PoolingHttpClientConnectionManager()
:无参构造方法,从源码中可以看到该方法调用了getDefaultRegistry()来注册http协议和https协议。常用方法:public void setMaxTotal(int max)
:该方法定义在ConnPoolControl接口中,表示设置最大连接数为max。public void setDefaultMaxPerRoute(int max)
:该方法也是定义在ConnPoolControl接口中,表示将每个路由的默认最大连接数设置为maxpublic void setMaxPerRoute(HttpRoute route,int max)
:设置某个指定路由的最大连接数,这个配置会覆盖setDefaultMaxPerRoute的某个路由的值。 - RequestConfig请求参数配置类
常用方法
static RequestConfig.Builder custom()
:静态方法,用于构建Builder 对象,然后设置相应的参数;int getConnectionRequestTimeout()
:获取从连接池获取连接的最长时间,单位是毫秒;int getConnectTimeout()
:获取创建连接的最长时间,单位是毫秒;int getSocketTimeout()
:获取数据传输的最长时间,单位是毫秒; RequestConfig有一个静态内部类Builder,用于构建RequestConfig对象并设置请求参数,该类有以下常用方法:
public RequestConfig.Builder setConnectionRequestTimeout(int connectionRequestTimeout)
:设置从连接池获取连接的最长时间,单位是毫秒;public RequestConfig.Builder setConnectTimeout(int connectTimeout)
:设置创建连接的最长时间,单位是毫秒;public RequestConfig.Builder setSocketTimeout(int socketTimeout)
:设置数据传输的最长时间,单位是毫秒; - HttpRequestRetryHandler请求重试接口
boolean retryRequest(IOException exception, int executionCount, org.apache.http.protocol.HttpContext context)
:实现该接口的,必须实现该方法,决定了如果一个方法执行时发生了IO异常,是否应该重试,重试executionCount次。
单线程-使用连接池管理HTTP请求
主要步骤:
- 创建HTTP的连接池管理对象cm,如下所示
1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示
1 //将最大连接数增加到2002 cm.setMaxTotal(200);3 //将每个路由的默认最大连接数增加到204 cm.setDefaultMaxPerRoute(20);
3.模拟发送HttpGet请求或者HttpPost请求,注意这里创建HttpClients对象的时候需要从连接池中获取,即要设置连接池对象,当执行发生IO异常需要处理时,要实现HttpRequestRetryHandler接口;需要注意的是这里处理完请求响应后,不能关闭HttpClient对象,如果关闭连接池也会销毁。HttpClients对象创建对象所示:
1 CloseableHttpClient httpClient = HttpClients.custom()2 .setConnectionManager(connectionManager)3 .setRetryHandler(retryHandler(5)).build();
4.可以开启线程来监听连接池中空闲连接,并清理无效连接,线程监听频率可以自行设置。
Java实现源码:1 package com.liangpj.develop.httpclient; 2 import org.apache.http.HttpEntityEnclosingRequest; 3 import org.apache.http.HttpRequest; 4 import org.apache.http.NoHttpResponseException; 5 import org.apache.http.client.HttpRequestRetryHandler; 6 import org.apache.http.client.config.RequestConfig; 7 import org.apache.http.client.methods.CloseableHttpResponse; 8 import org.apache.http.client.methods.HttpGet; 9 import org.apache.http.client.protocol.HttpClientContext; 10 import org.apache.http.conn.ConnectTimeoutException; 11 import org.apache.http.conn.HttpClientConnectionManager; 12 import org.apache.http.impl.client.CloseableHttpClient; 13 import org.apache.http.impl.client.HttpClients; 14 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 15 import org.apache.http.protocol.HttpContext; 16 import org.apache.http.util.EntityUtils; 17 import javax.net.ssl.SSLException; 18 import javax.net.ssl.SSLHandshakeException; 19 import java.io.IOException; 20 import java.io.InterruptedIOException; 21 import java.net.UnknownHostException; 22 23 /** 24 * 单线程-使用连接池管理HTTP请求 25 * @author: liangpengju 26 * @version: 1.0 27 */ 28 public class HttpConnectManager { 29 30 public static void main(String[] args) throws Exception { 31 //创建HTTP的连接池管理对象 32 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); 33 //将最大连接数增加到200 34 connectionManager.setMaxTotal(200); 35 //将每个路由的默认最大连接数增加到20 36 connectionManager.setDefaultMaxPerRoute(20); 37 //将http://www.baidu.com:80的最大连接增加到50 38 //HttpHost httpHost = new HttpHost("http://www.baidu.com",80); 39 //connectionManager.setMaxPerRoute(new HttpRoute(httpHost),50); 40 41 //发起3次GET请求 42 String url ="https://www.baidu.com/s?word=java"; 43 long start = System.currentTimeMillis(); 44 for (int i=0;i<100;i++){ 45 doGet(connectionManager,url); 46 } 47 long end = System.currentTimeMillis(); 48 System.out.println("consume -> " + (end - start)); 49 50 //清理无效连接 51 new IdleConnectionEvictor(connectionManager).start(); 52 } 53 54 /** 55 * 请求重试处理 56 * @param tryTimes 重试次数 57 * @return 58 */ 59 public static HttpRequestRetryHandler retryHandler(final int tryTimes){ 60 61 HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { 62 @Override 63 public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { 64 // 如果已经重试了n次,就放弃 65 if (executionCount >= tryTimes) { 66 return false; 67 } 68 // 如果服务器丢掉了连接,那么就重试 69 if (exception instanceof NoHttpResponseException) { 70 return true; 71 } 72 // 不要重试SSL握手异常 73 if (exception instanceof SSLHandshakeException) { 74 return false; 75 } 76 // 超时 77 if (exception instanceof InterruptedIOException) { 78 return false; 79 } 80 // 目标服务器不可达 81 if (exception instanceof UnknownHostException) { 82 return true; 83 } 84 // 连接被拒绝 85 if (exception instanceof ConnectTimeoutException) { 86 return false; 87 } 88 // SSL握手异常 89 if (exception instanceof SSLException) { 90 return false; 91 } 92 HttpClientContext clientContext = HttpClientContext .adapt(context); 93 HttpRequest request = clientContext.getRequest(); 94 // 如果请求是幂等的,就再次尝试 95 if (!(request instanceof HttpEntityEnclosingRequest)) { 96 return true; 97 } 98 return false; 99 }100 };101 return httpRequestRetryHandler;102 }103 104 /**105 * doGet106 * @param url 请求地址107 * @param connectionManager108 * @throws Exception109 */110 public static void doGet(HttpClientConnectionManager connectionManager,String url) throws Exception {111 //从连接池中获取client对象,多例112 CloseableHttpClient httpClient = HttpClients.custom()113 .setConnectionManager(connectionManager)114 .setRetryHandler(retryHandler(5)).build();115 116 // 创建http GET请求117 HttpGet httpGet = new HttpGet(url);118 // 构建请求配置信息119 RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) // 创建连接的最长时间120 .setConnectionRequestTimeout(500) // 从连接池中获取到连接的最长时间121 .setSocketTimeout(10 * 1000) // 数据传输的最长时间10s122 .setStaleConnectionCheckEnabled(true) // 提交请求前测试连接是否可用123 .build();124 // 设置请求配置信息125 httpGet.setConfig(config);126 127 CloseableHttpResponse response = null;128 try {129 // 执行请求130 response = httpClient.execute(httpGet);131 // 判断返回状态是否为200132 if (response.getStatusLine().getStatusCode() == 200) {133 String content = EntityUtils.toString(response.getEntity(), "UTF-8");134 System.out.println("内容长度:" + content.length());135 }136 } finally {137 if (response != null) {138 response.close();139 }140 // 此处不能关闭httpClient,如果关闭httpClient,连接池也会销毁141 // httpClient.close();142 }143 }144 145 /**146 * 监听连接池中空闲连接,清理无效连接147 */148 public static class IdleConnectionEvictor extends Thread {149 150 private final HttpClientConnectionManager connectionManager;151 152 private volatile boolean shutdown;153 154 public IdleConnectionEvictor(HttpClientConnectionManager connectionManager) {155 this.connectionManager = connectionManager;156 }157 158 @Override159 public void run() {160 try {161 while (!shutdown) {162 synchronized (this) {163 //3s检查一次164 wait(3000);165 // 关闭失效的连接166 connectionManager.closeExpiredConnections();167 }168 }169 } catch (InterruptedException ex) {170 // 结束171 ex.printStackTrace();172 }173 }174 175 public void shutdown() {176 shutdown = true;177 synchronized (this) {178 notifyAll();179 }180 }181 }182 }
多线程-HttpClient连接池管理HTTP请求实例
主要步骤:
- 创建HTTP的连接池管理对象cm,如下所示
1 PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
2.设置最大连接数和每个路由的默认最大连接数等参数,如下所示
1 //将最大连接数增加到2002 cm.setMaxTotal(200);3 //将每个路由的默认最大连接数增加到204 cm.setDefaultMaxPerRoute(20);
3.创建HttpClients对象的并设置连接池对象,HttpClients对象创建对象所示:
1 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();
4.继承Thread类实现一个执行Get请求线程类GetThread,重载run()方法,执行HttpGet请求;
5.定义要请求的URI地址为数组形式,为每一个URI创建一个GetThread线程,并启动所有线程;Java实现源码:1 package com.liangpj.develop.httpclient; 2 import org.apache.http.HttpEntity; 3 import org.apache.http.client.ClientProtocolException; 4 import org.apache.http.client.methods.CloseableHttpResponse; 5 import org.apache.http.client.methods.HttpGet; 6 import org.apache.http.client.protocol.HttpClientContext; 7 import org.apache.http.impl.client.CloseableHttpClient; 8 import org.apache.http.impl.client.HttpClients; 9 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;10 import org.apache.http.protocol.HttpContext;11 import java.io.IOException;12 13 /**14 * 多线程-HttpClient连接池管理HTTP请求实例15 */16 public class MultiThreadHttpConnManager {17 public static void main(String[] args) {18 //连接池对象19 PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();20 //将最大连接数增加到20021 connectionManager.setMaxTotal(200);22 //将每个路由的默认最大连接数增加到2023 connectionManager.setDefaultMaxPerRoute(20);24 //HttpClient对象25 CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build();26 //URIs to DoGet27 String[] urisToGet = {28 "https://www.baidu.com/s?word=java",29 "https://www.baidu.com/s?word=java",30 "https://www.baidu.com/s?word=java",31 "https://www.baidu.com/s?word=java"32 };33 //为每一个URI创建一个线程34 GetThread[] threads = new GetThread[urisToGet.length];35 for (int i=0;i
想要获取HttpClient实战的所有实例代码,可以关注Java技术日志订阅号后,在消息框回复关键字:httpclient可以获取代码的地址。