HttpClient 连接池管理

标签: dev | 发表时间:2017-11-18 08:00 | 作者:
出处:http://itindex.net/relian

随着微服务的流行,服务之间的http调用越来越多。在java里面我们可以使用httpclient这个开源工具类来进行处理,但若使用不当,可能性能会比较差,尤其是连接池是否能正常使用。接下来会详细分析下httpclient的连接池原理。

1. 使用httpclient的好处

1)、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗。
2)、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接。采用连接池来管理长连接,可以复用之前的连接,而我们使用httpclient,有可能是两个集群之间调用,也就是有限的机器之间进行调用。这样复用连接池,能有效的节约资源。

2. 长连接和短连接

因为httpclient连接池都是管理的基于长连接的socket连接。所以介绍下长连接和短连接的区别。首先需要说下http keep-alive与tcp keep-alive和区别,http keep-alive与tcp keep-alive,不是同一回事,意图不一样。http keep-alive是为了让tcp活得更久一点,以便在同一个连接上传送多个http,提高socket的效率。而tcp keep-alive是TCP的一种检测TCP连接状况的保鲜机制。tcp keep-alive保鲜定时器,支持三个系统内核配置参数:

   echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes

keepalive是TCP保鲜定时器,当网络两端建立了TCP连接之后,闲置idle(双方没有任何数据流发送往来)了tcp_keepalive_time后,服务器内核就会尝试向客户端发送侦测包,来判断TCP连接状况(有可能客户端崩溃、强制关闭了应用、主机不可达等等)。如果没有收到对方的回答(ack包),则会在 tcp_keepalive_intvl后再次尝试发送侦测包,直到收到对对方的ack,如果一直没有收到对方的ack,一共会尝试 tcp_keepalive_probes次,每次的间隔时间在这里分别是15s, 30s, 45s, 60s, 75s。如果尝试tcp_keepalive_probes,依然没有收到对方的ack包,则会丢弃该TCP连接。TCP连接默认闲置时间是2小时,一般设置为30分钟足够了。
在httpclient中管理http层 keep-alive,创建连接并获得请求结果后,会对该连接设置长连接策略,如在MinimalClientExec.java类中,

   httpProcessor.process(request, context);
            final HttpResponse response = requestExecutor.execute(request, managedConn, context);
            httpProcessor.process(response, context);

            // The connection is in or can be brought to a re-usable state.
            if (reuseStrategy.keepAlive(response, context)) {
                // Set the idle duration of this connection
                final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
                releaseTrigger.setValidFor(duration, TimeUnit.MILLISECONDS); // 如果是长连接,此处设置过期时间。
                releaseTrigger.markReusable();
            } else {
                releaseTrigger.markNonReusable();
            }

在看看默认的长连接设置策略:

   public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {

    public static final DefaultConnectionKeepAliveStrategy INSTANCE = new DefaultConnectionKeepAliveStrategy();

    public long getKeepAliveDuration(final HttpResponse response, final HttpContext context) {
        Args.notNull(response, "HTTP response");
        final HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            final HeaderElement he = it.nextElement();
            final String param = he.getName();
            final String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000; // 默认返回的是1S
                } catch(final NumberFormatException ignore) {
                }
            }
        }
        return -1;
    }

}

当然也可以自己实现ConnectionKeepAliveStrategy类,来实现自己的长连接策略。

   ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        // Honor 'keep-alive' header
        HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(NumberFormatException ignore) {
                }
            }
        }
        HttpHost target = (HttpHost) context.getAttribute(
                HttpClientContext.HTTP_TARGET_HOST);
                // 对特定的域名进行长连接设置
        if ("www.baidu.com".equalsIgnoreCase(target.getHostName())) {
            // Keep alive for 5 seconds only
            return 5 * 1000;
        } else {
            // otherwise keep alive for 30 seconds
            return 30 * 1000;
        }
    }
};
CloseableHttpClient client = HttpClients.custom()
        .setKeepAliveStrategy(myStrategy)// 设置为自己定义的长连接策略
        .build();

3. Httpclient连接池原理
连接池的结构如下:

其中CPoolEntry是连接实体,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,pending代表阻塞状态的连接。外层的leased,avaliabled,pending是全局的,用来限制全局的数量,里面有各个小的routeToPool,对应不同的server机器。
从连接池获取可用连接的过程为:
   1). 每个server对应一个routeToPool,从对应的routeToPool中获取可用的连接,有则返回该连接。若没有则转入下一步。
   2). 若routeToPool和外层CPool连接池均还有可用的空间,则新建连接,并将该连接作为可用连接返回;否则进行下一步
   3). 将当前请求放入pending队列,等待执行。
   4). 上述过程中会判断各种条件是否满足,比如不能超过我们设置的总连接数等。

CPool继承的基类:AbstractConnPool,其代码结构如下

      public abstract class AbstractConnPool<T, C, E extends PoolEntry<T, C>>  
                                                   implements ConnPool<T, E>, ConnPoolControl<T> {  

        private final Lock lock;  
        private final ConnFactory<T, C> connFactory;  
        private final Map<T, RouteSpecificPool<T, C, E>> routeToPool;  //路由和连接之间的对应关系
        private final Set<E> leased;  // 被占用的连接
        private final LinkedList<E> available;  // 可用连接
        private final LinkedList<PoolEntryFuture<E>> pending;  
        private final Map<T, Integer> maxPerRoute;  

        private volatile boolean isShutDown;  
        private volatile int defaultMaxPerRoute;  
        private volatile int maxTotal;  
        private volatile int validateAfterInactivity;

从上述代码可以看出CPoolEntry便是线程池里面的一个个元素,CPool里面包含routeToPool这个小的线程池,routeToPool里面都是相对于一个固定的HttpRoute(也可以说到一个固定的机器地址)所建立的所有连接。

4. HttpClient参数配置
HttpClient有多个参数可以配置,比如:
PoolingClientConnectionManager conMgr = new PoolingClientConnectionManager();
conMgr.setMaxTotal(200); //设置整个连接池最大连接数,每个routeToPool的大小加起来不能超过maxTotal的值。
conMgr.setDefaultMaxPerRoute(20);//这个便是设置的routeToPool线程池的大小。
还可以设置请求超时时间,socket等待数据超时时间,从连接池获取不到数据时的等待时间。


通过以上分析可知,通过合理的设置连接池的长连接策略,连接池的大小,以及一些连接相关的参数,能有效的提高系统的并发量。HttpClient的其它一些特性诸如自动管理cookie,支持HTTPS协议,以可扩展的面向对象的结构实现了http的全部方法等,增加了易用性和灵活性,也方便了我们的使用。


相关 [httpclient 管理] 推荐:

HttpClient 连接池管理

- - IT瘾-dev
随着微服务的流行,服务之间的http调用越来越多. 在java里面我们可以使用httpclient这个开源工具类来进行处理,但若使用不当,可能性能会比较差,尤其是连接池是否能正常使用. 接下来会详细分析下httpclient的连接池原理. 使用httpclient的好处. 1)、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗.

HttpClient 与 Close_Wait

- - 互联网 - ITeye博客
服务器A需要通过HttpClient去连接另一个系统B提供的服务,运行一段时间后抛出以下异常:. 在服务器B上运行netstat命令,发现大量连接处于 CLOSE_WAIT 状态. 简单来说CLOSE_WAIT数目过大是由于被动关闭连接处理不当导致的. 我说一个场景,服务器A会去请求服务器B上面的apache获取文件资源,正常情况下,如果请求成功,那么在抓取完资源后服务器A会主动发出关闭连接的请求,这个时候就是主动关闭连接,连接状态我们可以看到是TIME_WAIT.

Httpclient远程调用WebService示例(Eclipse+httpclient)

- - 企业架构 - ITeye博客
我们将Web Service发布在Tomcat或者其他应用服务器上后,有很多方法可以调用该Web Service,常用的有两种:.       1、通过浏览器HTTP调用,返回规范的XML文件内容.       2、通过客户端程序调用,返回结果可自定义格式.       接下来,我利用Eclipse作为开发工具,演示一个Httpclient调用WebService的简单示例.

HttpClient使用详解

- - CSDN博客推荐文章
HttpClient:是一个接口. 首先需要先创建一个DefaultHttpClient的实例. 先创建一个HttpGet对象,传入目标的网络地址,然后调用HttpClient的execute()方法即可:. 创建一个HttpPost对象,传入目标的网络地址:. 通过一个NameValuePair集合来存放待提交的参数,并将这个参数集合传入到一个UrlEncodedFormEntity中,然后调用HttpPost的setEntity()方法将构建好的UrlEncodedFormEntity传入:.

Apache HttpClient 4.3开发指南

- - CSDN博客推荐文章
《Apache HttpClient 4.3开发指南》. 作者:chszs,转载需注明. 博客主页: http://blog.csdn.net/chszs. Apache HttpClient 4系列已经发布很久了,但由于它与HttpClient 3.x版本完全不兼容,以至于业内采用此库的公司较少,在互联网上也少有相关的文档资料分享.

Android HttpURLConnection及HttpClient选择

- - Trinea
介绍Android中Http请求方式的选择、区别及几个常用框架对API的选择. Android Http请求API主要分两种:. 第一种是Java的HttpURLConnection,默认带gzip压缩. 第二种Apache的HttpClient,默认不带gzip压缩. 两种方式请求connection都是keep alive,默认User-Agent不同.

配置使用连接池的httpClient

- - 企业架构 - ITeye博客
httpClient4.3开始弃用了DefaultHttpClient和PoolingClientConnectionManager.原来这样配置的连接工厂. 现在改用spring推荐的类,配置如下:.   配置带有连接池的org.springframework.web.client.RestTemplate.

HttpUrlconnection 、Httpclient get 、post 请求核心代码

- - CSDN博客推荐文章
HttpURLConnection的使用  . * URL请求的类别分为二类,GET与POST请求. * a:) get请求可以获取静态页面,也可以把参数放在URL字串后面,传递给servlet, . * b:) post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内.

Java中httpClient中三种超时设置

- - CSDN博客推荐文章
本文章给大家介绍一下关于Java中httpClient中的三种超时设置小结. 在Apache的HttpClient包中,有三个设置超时的地方:. /* 从连接池中取连接的超时时间*/ ConnManagerParams.setTimeout(params, 1000); /*连接超时*/ HttpConnectionParams.setConnectionTimeout(params, 2000); /*请求超时*/ HttpConnectionParams.setSoTimeout(params, 4000);.

关于httpclient 请求https (如何绕过证书验证)

- - BlogJava_首页
第一种方法,适用于httpclient4.X 里边有get和post两种方法供你发送请求使用. 导入证书发送请求的在这里就不说了,网上到处都是.      * 访问https的网站 .         //调用ssl  .      * 重写验证方法,取消检测ssl .    // 创建HttpClient实例     .