AI智能
改变未来

HTTP连接池

1.为什么要用Http连接池

1、降低延迟:如果不采用连接池,每次连接发起Http请求的时候都会重新建立TCP连接(经历3次握手),用完就会关闭连接(4次挥手),如果采用连接池则减少了这部分时间损耗,别小看这几次握手,本人经过测试发现,基本上3倍的时间延迟

2、支持更大的并发:如果不采用连接池,每次连接都会打开一个端口,在大并发的情况下系统的端口资源很快就会被用完,导致无法建立新的连接

2.简单连接管理器

BasicHttpClientConnectionManager

是个简单的连接管理器,它一次只能管理一个连接。尽管这个类是线程安全的,它在同一时间也只能被一个线程使用。

BasicHttpClientConnectionManager

会尽量重用旧的连接来发送后续的请求,并且使用相同的路由。如果后续请求的路由和旧连接中的路由不匹配,

BasicHttpClientConnectionManager

就会关闭当前连接,使用请求中的路由重新建立连接。如果当前的连接正在被占用,会抛出

java.lang.IllegalStateException

异常。

3.连接池管理器

相对

BasicHttpClientConnectionManager

来说,

PoolingHttpClientConnectionManager

是个更复杂的类,它管理着连接池,可以同时为很多线程提供http连接请求。Connections are pooled on a per route basis.当请求一个新的连接时,如果连接池有有可用的持久连接,连接管理器就会使用其中的一个,而不是再创建一个新的连接。

PoolingHttpClientConnectionManager

维护的连接数在每个路由基础和总数上都有限制。默认,每个路由基础上的连接不超过2个,总连接数不能超过20。在实际应用中,这个限制可能会太小了,尤其是当服务器也使用Http协议时。

下面的例子演示了如果调整连接池的参数:

    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();    // 将最大连接数增加到200    cm.setMaxTotal(200);    // 将每个路由基础的连接增加到20    cm.setDefaultMaxPerRoute(20);    //将目标主机的最大连接数增加到50    HttpHost localhost = new HttpHost(\"www.yeetrack.com\", 80);    cm.setMaxPerRoute(new HttpRoute(localhost), 50);    CloseableHttpClient httpClient = HttpClients.custom()            .setConnectionManager(cm)            .build();

4.关闭连接管理器

当一个HttpClient的实例不在使用,或者已经脱离它的作用范围,我们需要关掉它的连接管理器,来关闭掉所有的连接,释放掉这些连接占用的系统资源。

    CloseableHttpClient httpClient = <...>    httpClient.close();

5. 连接回收策略

经典阻塞I/O模型的一个主要缺点就是只有当组侧I/O时,socket才能对I/O事件做出反应。当连接被管理器收回后,这个连接仍然存活,但是却无法监控socket的状态,也无法对I/O事件做出反馈。如果连接被服务器端关闭了,客户端监测不到连接的状态变化(也就无法根据连接状态的变化,关闭本地的socket)。

HttpClient为了缓解这一问题造成的影响,会在使用某个连接前,监测这个连接是否已经过时,如果服务器端关闭了连接,那么连接就会失效。这种过时检查并不是100%有效,并且会给每个请求增加10到30毫秒额外开销。唯一一个可行的,且does not involve a one thread per socket model for idle connections的解决办法,是建立一个监控线程,来专门回收由于长时间不活动而被判定为失效的连接。这个监控线程可以周期性的调用

ClientConnectionManager

类的

closeExpiredConnections()

方法来关闭过期的连接,回收连接池中被关闭的连接。它也可以选择性的调用

ClientConnectionManager

类的

closeIdleConnections()

方法来关闭一段时间内不活动的连接。

    public static class IdleConnectionMonitorThread extends Thread {        private final HttpClientConnectionManager connMgr;        private volatile boolean shutdown;        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {            super();            this.connMgr = connMgr;        }        @Override        public void run() {            try {                while (!shutdown) {                    synchronized (this) {                        wait(5000);                        // 关闭失效的连接                        connMgr.closeExpiredConnections();                        // 可选的, 关闭30秒内不活动的连接                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);                    }                }            } catch (InterruptedException ex) {                // terminate            }        }        public void shutdown() {            shutdown = true;            synchronized (this) {                notifyAll();            }        }    }

6. 连接存活策略

Http规范没有规定一个持久连接应该保持存活多久。有些Http服务器使用非标准的

Keep-Alive

头消息和客户端进行交互,服务器端会保持数秒时间内保持连接。HttpClient也会利用这个头消息。如果服务器返回的响应中没有包含

Keep-Alive

头消息,HttpClient会认为这个连接可以永远保持。然而,很多服务器都会在不通知客户端的情况下,关闭一定时间内不活动的连接,来节省服务器资源。在某些情况下默认的策略显得太乐观,我们可能需要自定义连接存活策略。

    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.naughty-server.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();

7.原理及注意事项

连接池中连接都是在发起请求的时候建立,并且都是长连接

in.close();作用就是将用完的连接释放,下次请求可以复用,这里特别注意的是,如果不使用in.close();而仅仅使用response.close();结果就是连接会被关闭,并且不能被复用,这样就失去了采用连接池的意义。

连接池释放连接的时候,并不会直接对TCP连接的状态有任何改变,只是维护了两个Set,leased和avaliabled,leased代表被占用的连接集合,avaliabled代表可用的连接的集合,释放连接的时候仅仅是将连接从leased中remove掉了,并把连接放到avaliabled集合中

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » HTTP连接池