前言

使用apache的httpclient进行http的交互处理已经很长时间了,而httpclient实例则使用了http连接池,想必大家也没有关心过连接池的管理。事实上,通过分析httpclient源码,发现它很优雅地隐藏了所有的连接池管理细节,开发者完全不用花太多时间去思考连接池的问题。

Apache官网例子

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/");
CloseableHttpResponse response = httpclient.execute(httpget);
try {HttpEntity entity = response.getEntity();if (entity != null) {long len = entity.getContentLength();if (len != -1 && len < 2048) {System.out.println(EntityUtils.toString(entity));} else {// Stream content out}}
} finally {response.close();
}

HttpClient及其连接池配置

  • 整个线程池中最大连接数 MAX_CONNECTION_TOTAL = 800
  • 路由到某台主机最大并发数,是MAX_CONNECTION_TOTAL(整个线程池中最大连接数)的一个细分 ROUTE_MAX_COUNT = 500
  • 重试次数,防止失败情况 RETRY_COUNT = 3
  • 客户端和服务器建立连接的超时时间 CONNECTION_TIME_OUT = 5000
  • 客户端从服务器读取数据的超时时间 READ_TIME_OUT = 7000
  • 从连接池中获取连接的超时时间 CONNECTION_REQUEST_TIME_OUT = 5000
  • 连接空闲超时,清楚闲置的连接 CONNECTION_IDLE_TIME_OUT = 5000
  • 连接保持存活时间 DEFAULT_KEEP_ALIVE_TIME_MILLIS = 20 * 1000

MaxtTotal和DefaultMaxPerRoute的区别

  • MaxtTotal是整个池子的大小;
  • DefaultMaxPerRoute是根据连接到的主机对MaxTotal的一个细分;

比如:MaxtTotal=400,DefaultMaxPerRoute=200,而我只连接到http://hjzgg.com时,到这个主机的并发最多只有200;而不是400;而我连接到http://qyxjj.com 和 http://httls.com时,到每个主机的并发最多只有200;即加起来是400(但不能超过400)。所以起作用的设置是DefaultMaxPerRoute。

HttpClient连接池模型

HttpClient从连接池中获取连接分析

org.apache.http.pool.AbstractConnPool

private E getPoolEntryBlocking(final T route, final Object state,final long timeout, final TimeUnit tunit,final PoolEntryFuture<E> future)throws IOException, InterruptedException, TimeoutException {Date deadline = null;if (timeout > 0) {deadline = new Date(System.currentTimeMillis() + tunit.toMillis(timeout));}this.lock.lock();try {final RouteSpecificPool<T, C, E> pool = getPool(route);//这是每一个路由细分出来的连接池E entry = null;while (entry == null) {Asserts.check(!this.isShutDown, "Connection pool shut down");//从池子中获取一个可用连接并返回for (;;) {entry = pool.getFree(state);if (entry == null) {break;}if (entry.isExpired(System.currentTimeMillis())) {entry.close();} else if (this.validateAfterInactivity > 0) {if (entry.getUpdated() + this.validateAfterInactivity <= System.currentTimeMillis()) {if (!validate(entry)) {entry.close();}}}if (entry.isClosed()) {this.available.remove(entry);pool.free(entry, false);} else {break;}}if (entry != null) {this.available.remove(entry);this.leased.add(entry);onReuse(entry);return entry;}//创建新的连接// New connection is neededfinal int maxPerRoute = getMax(route);//获取当前路由最大并发数// Shrink the pool prior to allocating a new connectionfinal int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);if (excess > 0) {//如果当前路由对应的连接池的连接超过最大路由并发数,获取到最后使用的一次连接,释放掉for (int i = 0; i < excess; i++) {final E lastUsed = pool.getLastUsed();if (lastUsed == null) {break;}lastUsed.close();this.available.remove(lastUsed);pool.remove(lastUsed);}}//尝试创建新的连接 if (pool.getAllocatedCount() < maxPerRoute) {//当前路由对应的连接池可用空闲连接数+当前路由对应的连接池已用连接数 < 当前路由对应的连接池最大并发数final int totalUsed = this.leased.size();final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);if (freeCapacity > 0) {final int totalAvailable = this.available.size();if (totalAvailable > freeCapacity - 1) {//线程池中可用空闲连接数 > (线程池中最大连接数 - 线程池中已用连接数 - 1)if (!this.available.isEmpty()) {final E lastUsed = this.available.removeLast();lastUsed.close();final RouteSpecificPool<T, C, E> otherpool = getPool(lastUsed.getRoute());otherpool.remove(lastUsed);}}final C conn = this.connFactory.create(route);entry = pool.add(conn);this.leased.add(entry);return entry;}}boolean success = false;try {pool.queue(future);this.pending.add(future);success = future.await(deadline);} finally {// In case of 'success', we were woken up by the// connection pool and should now have a connection// waiting for us, or else we're shutting down.// Just continue in the loop, both cases are checked.pool.unqueue(future);this.pending.remove(future);}// check for spurious wakeup vs. timeoutif (!success && (deadline != null) &&(deadline.getTime() <= System.currentTimeMillis())) {break;}}throw new TimeoutException("Timeout waiting for connection");} finally {this.lock.unlock();}
}

连接重用和保持策略

http的长连接复用, 其判定规则主要分两类。
1. http协议支持+请求/响应header指定
2. 一次交互处理的完整性(响应内容消费干净)
对于前者, httpclient引入了ConnectionReuseStrategy来处理, 默认的采用如下的约定:

  • HTTP/1.0通过在Header中添加Connection:Keep-Alive来表示支持长连接。
  • HTTP/1.1默认支持长连接, 除非在Header中显式指定Connection:Close, 才被视为短连接模式。

HttpClientBuilder创建MainClientExec

ConnectionReuseStrategy(连接重用策略)

org.apache.http.impl.client.DefaultClientConnectionReuseStrategy

MainClientExec处理连接

处理完请求后,获取到response,通过ConnectionReuseStrategy判断连接是否可重用,如果是通过ConnectionKeepAliveStrategy获取到连接最长有效时间,并设置连接可重用标记。

连接重用判断逻辑

  • request首部中包含Connection:Close,不复用
  • response中Content-Length长度设置不正确,不复用
  • response首部包含Connection:Close,不复用
  • reponse首部包含Connection:Keep-Alive,复用
  • 都没命中的情况下,如果HTTP版本高于1.0则复用

更多参考:https://www.cnblogs.com/mumuxinfei/p/9121829.html

连接释放原理分析

HttpClientBuilder会构建一个InternalHttpClient实例,也是CloseableHttpClient实例。InternalHttpClient的doExecute方法来完成一次request的执行。

会继续调用MainClientExec的execute方法,通过连接池管理者获取连接(HttpClientConnection)。

构建ConnectionHolder类型对象,传递连接池管理者对象和当前连接对象。

请求执行完返回HttpResponse类型对象,然后包装成HttpResponseProxy对象(是CloseableHttpResponse实例)返回。

CloseableHttpClient类其中一个execute方法如下,finally方法中会调用HttpResponseProxy对象的close方法释放连接。

最终调用ConnectionHolder的releaseConnection方法释放连接。

CloseableHttpClient类另一个execute方法如下,返回一个HttpResponseProxy对象(是CloseableHttpResponse实例)。

这种情况下调用者获取了HttpResponseProxy对象,可以直接拿到HttpEntity对象。大家关心的就是操作完HttpEntity对象,使用完InputStream到底需不需要手动关闭流呢?

其实调用者不需要手动关闭流,因为HttpResponseProxy构造方法里有增强HttpEntity的处理方法,如下。

调用者最终拿到的HttpEntity对象是ResponseEntityProxy实例。

ResponseEntityProxy重写了获取InputStream的方法,返回的是EofSensorInputStream类型的InputStream对象。

EofSensorInputStream对象每次读取都会调用checkEOF方法,判断是否已经读取完毕。

checkEOF方法会调用ResponseEntityProxy(实现了EofSensorWatcher接口)对象的eofDetected方法。

EofSensorWatcher#eofDetected方法中会释放连接并关闭流。

综上,通过CloseableHttpClient实例处理请求,无需调用者手动释放连接。

HttpClient在Spring中应用

创建ClientHttpRequestFactory

@Bean
public ClientHttpRequestFactory clientHttpRequestFactory()throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build();httpClientBuilder.setSSLContext(sslContext).setMaxConnTotal(MAX_CONNECTION_TOTAL).setMaxConnPerRoute(ROUTE_MAX_COUNT).evictIdleConnections(CONNECTION_IDLE_TIME_OUT, TimeUnit.MILLISECONDS);httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, true));httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());CloseableHttpClient client = httpClientBuilder.build();HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);clientHttpRequestFactory.setConnectTimeout(CONNECTION_TIME_OUT);clientHttpRequestFactory.setReadTimeout(READ_TIME_OUT);clientHttpRequestFactory.setConnectionRequestTimeout(CONNECTION_REQUEST_TIME_OUT);clientHttpRequestFactory.setBufferRequestBody(false);return clientHttpRequestFactory;
}

创建RestTemplate

@Bean
public RestTemplate restTemplate()  {RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());restTemplate.setErrorHandler(new DefaultResponseErrorHandler());// 修改StringHttpMessageConverter内容转换器restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));return restTemplate;
}

Spring官网例子

@SpringBootApplication
public class Application {private static final Logger log = LoggerFactory.getLogger(Application.class);public static void main(String args[]) {SpringApplication.run(Application.class);}@Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder) {return builder.build();}@Beanpublic CommandLineRunner run(RestTemplate restTemplate) throws Exception {return args -> {Quote quote = restTemplate.getForObject("https://gturnquist-quoters.cfapps.io/api/random", Quote.class);log.info(quote.toString());};}
}

总结

Apache的HttpClient组件可谓良心之作,细细的品味一下源码可以学到很多设计模式和比编码规范。不过在阅读源码之前最好了解一下不同版本的HTTP协议,尤其是HTTP协议的Keep-Alive模式。使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服 务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。这里推荐一篇参考链接:https://www.jianshu.com/p/49551bda6619。

连接池超时配置_HttpClient连接池的一些思考相关推荐

  1. 使用FileZilla连接时超时,无法连接到服务器

    使用FileZilla连接时超时,无法连接到服务器 我最近修改了点东西,然后提示我审核未通过,这玩意是干嘛的你都不清楚,我也是醉了 用FileZilla连接服务器时,显示错误: 错误:20秒后无活动, ...

  2. javaweb mysql 连接池 c3p0 配置_javaWeb_JDBC_c3p0数据库连接池

    JDBC_c3p0数据库连接池 1.一个基本的c3p0数据库连接池 /** * 一个基本的从池c3p0数据库连接池 */ public static void testC3P0() throws Ex ...

  3. spss连接mysql_spssstatistics19.0配置odbc连接远程oracle数据库

    本文档讲的是spss statistics 19.0如何配置odbc连接本地oracle数据库,步骤如下: 1.开启远程oracle数据库服务,开启以下两个即可 2.在本地客户端中,安装好oracle ...

  4. weblogic连接oracle配置文件,配置weblogic连接oracle的数据源

    Name:JDBC-DEV1 JNDI Name:jdbc.datasource.DS1 AdminServer Row Prefetch Size:  48 Stream Chunk Size:25 ...

  5. myeclipse2014 mysql连接池_myeclips配置mysql连接池

    http://gstarwd.iteye.com/blog/542845 第一: 首先声明,这些配置过程只适合Tomcat6.0,因为Tomcat6.0以下的版本,配置过程有那么一点不一样!Tomca ...

  6. myeclipse2014 mysql连接池_MyEclipse配置数据源连接池

    转自http://www.cnblogs.com/caodajieup/archive/2010/07/26/1785202.html 软件版本myeclispe8.0,自带tomcat6.0.13. ...

  7. javaweb mysql 连接池 c3p0 配置_C3P0连接池详细配置与实现(2)全局使用

    全局使用c3p0 1. server.xml中GlobalNamingResources description="DB Connection" driverClass=" ...

  8. hive连接mysql的配置_Dbeaver连接Hive和Mysql的配置

    1.连接Hive 首选需要配置Hive 这里我们采用的是JDBC的连接方式 (1) 在Hive中后台启动hiveserver2 [root@hadoop-101 hive]# bin/hiveserv ...

  9. linux远程连接 ipv6,Ansible 配置 IPv6 连接

    8种机械键盘轴体对比 本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选? 背景 平时工作中因为通常需要远程操作较多 Linux 环境,引入了 Ansible,但是大都是通过 Ansible 远程 ...

最新文章

  1. 2009年国内十强开源CMS排行榜[转]
  2. 亲身经历,在实验室不要乱按按钮!!
  3. angular.forEach
  4. 第十六届全国大学生智能汽车竞赛航天智慧物流总决赛暨第二届“航天杯”移动机器人AI创新技术挑战赛圆满举办
  5. CentOS常用指令
  6. c# ioc 单例模式_Spring-IOC
  7. 计算机无法同步时间,为什么在Win7中无法同步计算机时间?
  8. 我的python之路(二):python环境安装
  9. pytorch之学习率变化策略之MultiplicativeLR
  10. NLPIR汉语分词(Java调用 使用IDEA )
  11. 如何用SOLIDWORKS实现文字雕刻?来试试这样做!
  12. Android Studio gradle 统一版本管理
  13. c++中的trivial
  14. Linux修改只读文件(Read-Only)的方法
  15. android mvvm
  16. 三张简图搞懂GBDT
  17. 测试人员如何提高API功能测试效率?
  18. 《Multi-Scale Residual Learning-using a Cycle Spinning CNN for Single Image De-Raining》
  19. 【例题】系统中原有三类资源A、B、C和五个进程P1、P2、P3、P4、P5,A资源17,B资源5,C资源20。当前(TO时刻)系统资源分配和进程最大需求如下表。
  20. 合工大计算机录取分数,合肥工业大学适合“捡漏”的专业,录取分数最低

热门文章

  1. C#获取刚插入的数据的id
  2. idea 如何隐藏/展示不想看到的文件
  3. 关于缓存的几篇好文章
  4. Oil Deposit
  5. poj 3348 Cows 求凸包以及凸包的面积
  6. 【评论】GNU/Linux下有多少是GNU的?
  7. r语言处理数据集编码_在强调编码语言或工具之前,请学习这3个基本数据概念
  8. Siamese网络(孪生神经网络)详解
  9. 第一章第一个c#程序上机_我从第一个#100DaysOfCode中学到的东西
  10. 如何在开源社区贡献代码_如何在15分钟内从浏览器获得您的第一个开源贡献