「这是我参与11月更文挑战的第18天,活动详情查看: 2021最后一次更文挑战 」
1. http请求存在的问题
Http1.0连接是无状态的,短链接,每次请求时建立连接、请求结束后断开连接。也可以在请求头中设置
Connection:keep-alive
来实现Http的长连接。
Http连接的建立和关闭本质上就是TCP连接的建立和关闭,在建立和关闭时会有三次握手和四次挥手的过程,占用资源多、开销大。
为了降低频繁建立和断开Http连接对资源的消耗,就需要使用Http连接池来管理Http的连接,并保证一定数量的长连接,且系统能拥有更高的并发性能。
2. 创建http连接池
HttpClient中创建http连接池的方式是使用定义的
PoolingHttpClientConnectionManager
类,该类实现了
HttpClientConnectionManager
接口和
ConnPoolControl
接口。
/** http连接池对象 */
private static PoolingHttpClientConnectionManager cm;
* 初始化连接池
public static void init(){
//创建http连接池,可以同时指定连接超时时间
cm = new PoolingHttpClientConnectionManager(60000, TimeUnit.MILLISECONDS);
//最多同时连接20个请求
cm.setMaxTotal(20);
//每个路由最大连接数,路由指IP+PORT或者域名
cm.setDefaultMaxPerRoute(50);
3. 通过连接池获取httpClient
HttpClient中http请求的连接由HttpClient对象发起,加入连接池后就可以从连接池中获取HttpClient对象信息。
* 从连接池中获取httpClient连接
public static CloseableHttpClient getHttpClient(PoolingHttpClientConnectionManager cm){
//设置请求参数配置,创建连接时间、从连接池获取连接时间、数据传输时间、是否测试连接可用、构建配置对象
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(1000)
.setConnectionRequestTimeout(3000)
.setSocketTimeout(10 * 1000)
.setStaleConnectionCheckEnabled(true)
.build();
//创建httpClient时从连接池中获取,并设置连接失败时自动重试(也可以自定义重试策略:setRetryHandler())
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(cm)
.disableAutomaticRetries()
.build();
return httpClient;
在使用http连接池获取httpClient连接时,需要配置相关参数信息:
定义RequestConfig
对象来设置请求时的时间参数,如连接创建时间、从连接池中获取连接时间、数据传输请求时间、是否请求前测试可用性等
RequestConfig
对象可以为整个httpClient设置,也可以针对GET请求和POST请求分别设置
接收一个CloseableHttpClient
对象作为httpClient,使用建造者模式获取时需要设置连接对象的配置信息、失败重连信息以及来源的连接池信息。
4. 设定请求类型并执行请求
创建httpClient连接后,之后的请求执行流程和单独创建httpClient对象的请求流程基本一致。
需要注意的就是在使用连接池获取的httpClient请求完成后,如果希望将连接释放到连接池中,就不能使用close()方法关闭,而是调用EntityUtils.consume()方法。
* 执行请求
public static void doGetRequest(CloseableHttpClient httpClient,String url){
//创建http请求类型
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse httpResponse = null;
try {
httpResponse = httpClient.execute(httpGet);
if(200 == httpResponse.getStatusLine().getStatusCode()){
System.out.println("请求返回数据内容:" + EntityUtils.toString(httpResponse.getEntity()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if(httpResponse != null){
//执行httpResponse.close关闭对象会关闭连接池,
//如果需要将连接释放到连接池,可以使用EntityUtils.consume()方法
try {
EntityUtils.consume(httpResponse.getEntity());
} catch (IOException e) {
e.printStackTrace();
5. 连接池状态观察
使用连接池获取httpClient连接发起请求时,可以通过连接池中连接的存活等信息来观察使用情况。
当使用单线程执行多次http请求时,连接池中始终保持一个连接,因为单线程总是执行完上一个才会开始下一个请求的执行。
String url = "http://www.baidu.com";
for(int i = 0; i < 5; i++){
//连接池中获取httpClient
CloseableHttpClient httpClient = getHttpClient(cm);
//单线程执行请求
doGetRequest(httpClient, url);
System.out.println(cm.getTotalStats());
因为是单线程,线程池的状态会打印5次,每次结果都是//[leased: 0; pending: 0; available: 1; max: 20]
而使用多线程执行http请求时,每个请求开启一个线程后,最终执行完成并将连接释放至连接池后,连接池中存活了请求中并存的最大连接数。
String url = "http://www.baidu.com";
for(int i = 0; i < 5; i++){
//获取httpClient
CloseableHttpClient httpClient = getHttpClient(cm);
//每个请求开启一个单独的线程
doGetRequestMulitThread(httpClient,url);
System.out.println(cm.getTotalStats());