HttpClient学习——2.连接管理

本文介绍了HttpClient的连接管理,包括HttpClientConnectionManager接口的作用,BasicHttpClientConnectionManager的一次性连接特性,以及PoolingHttpClientConnectionManager的连接池原理和参数设置。重点探讨了连接池的结构,连接的获取和释放过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2.1 连接管理器HttpClientConnectionManager

首先介绍路由(HttpRoute)的概念,我们可以理解成一条连接的路线。如果多个线程访问的是同一个IP+端口,我们就认为他们实际上使用的是同一个HttpRoute。
HTTP连接不是一个线程安全的对象,因此一个HTTP连接一次只能由一个线程使用。HttpClient使用HttpClientConnectionManager接口来管理Http连接,它管理Http连接的生命周期和线程安全,保证一个连接至多有一个线程访问。

2.1.1* BasicHttpClientConnectionManager

一次只维护一个连接,因此它只能被一个线程使用。如果持久连接的路由与连接请求的路由不匹配,则会关闭现有连接并重新打开给定路由。如果连接已经被分配,则抛出java.lang.IllegalStateException。

可以理解成MaxTotal=1,DefaultMaxPerRoute=1的PoolingHttpClientConnectionManager。

2.1.2 PoolingHttpClientConnectionManager

通过名字就可以知道这是一个Http连接池。它将连接按照路由进行合并,如果一个请求的路由在池中存在并且有空闲的连接,就复用连接池中的连接而不是创建新的连接。

连接池有两个重要的参数,那就是连接池中连接上限MaxTotal和每一个路由的连接上限DefaultMaxPerRoute。默认参数的MaxTotal=20,DefaultMaxPerRoute=2。这个一般都不能够满足我们生产环境的需要,因此在使用连接池时有必要根据需要自己设置这两个参数。

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);

CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();

2.1.3 连接池原理

2.1.3.1 Http连接池的结构

下面中的代码选自HttpClient的源码:
连接池的结构:

private final Map<T, RouteSpecificPool<T, C, E>> routeToPool;       //每个route有一个自己的连接池
private final Set<E> leased;                          //已经使用的连接集合
private final LinkedList<E> available;                    //空闲连接集合   
private final Map<T, Integer> maxPerRoute;                //每个route的连接最大数
private volatile int defaultMaxPerRoute;                //默认每个route的最大连接数
private volatile int maxTotal;                          //连接池连接上限

我们可以看到连接池结构有一个空闲连接池的集合和一个已使用连接池的集合,此外对每个route都有自己的一个连接池。

2.1.3.2 获取连接

从连接池获取连接的过程简单来说就是从available集合中拿出一个连接放入leased集合中。详细的过程见下面的代码,如果不愿意看代码可以直接看注释:

从连接池获取连接的过程

final RouteSpecificPool<T, C, E> pool = getPool(route);
E entry;
for (;;) {
    Asserts.check(!this.isShutDown, "Connection pool shut down");
    for (;;) {              //遍历route的连接池,查找其中没有close掉的连接
        entry = pool.getFree(state);
        if (entry == null) {
            break;
        }
        if (entry.isExpired(System.currentTimeMillis())) {
            entry.close();
        }
        if (entry.isClosed()) {     //关闭连接池中已经close的连接
            this.available.remove(entry);
            pool.free(entry, false);
        } else {
            break;
        }
    }
    if (entry != null) {            //连接池中有可以复用的连接,对available和leased两个集合进行操作
        this.available.remove(entry);
        this.leased.add(entry);
        onReuse(entry);
        return entry;
    }

    //...

    if (pool.getAllocatedCount() < maxPerRoute) {    //route的连接池未满,创建新连接
        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) {
                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;

2.1.3.3 释放连接

和获取连接相对的,释放连接的过程简单来说就是从leased集合中拿出一个连接放入available集合中。具体逻辑见下面的代码:

释放连接

public void release(final E entry, final boolean reusable) {
    this.lock.lock();
    try {
        if (this.leased.remove(entry)) {
            final RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
            pool.free(entry, reusable);             //free在route的连接池中将连接移动到available
            if (reusable && !this.isShutDown) {     //可复用,将连接移动到available
                this.available.addFirst(entry);
            } else {
                entry.close();          //不可复用,关闭连接
            }
            onRelease(entry);
            Future<E> future = pool.nextPending();
            if (future != null) {
                this.pending.remove(future);
            } else {
                future = this.pending.poll();
            }
            if (future != null) {
                this.condition.signalAll();
            }
        }
    } finally {
        this.lock.unlock();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值