置TOMCAT及httpClient的keepalive以高效利用长连接

配置TOMCAT及httpClient的keepalive以高效利用长连接 

总所周知http1.1(http1.0不是标准,依服务器而定)是支持长连接的,长连接能够保证服务器和客户端的socket能够高效利用,减少握手等额外的开销。httpClient在正常情况下会带上Connection: keep-alive表示我是支持长连接的,当完成一个请求后,视情况决定是否关闭连接。 

正常情况下,服务器在返回内容的头中会带上如下信息: 
配置TOMCAT及httpClient的keepalive以高效利用长连接 - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客 


但是如果服务器端连接次数计数达到指定值时,则会在返回内容中添加Connection: close信息,表示该连接将被关闭。 
配置TOMCAT及httpClient的keepalive以高效利用长连接 - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客 


这个时候该connection就失效了,客户端如果下次请求服务器的话,需要重新创建一个新的连接。但是有个问题,是否连接保持是由服务器端决定的,一旦连接超时导致connection close,服务器是不会专门通知客户端的,所以我们不能依赖Connection: close信息来决定是否关闭socket。我们先来看看tomcat对长连接的配置。下面是一个典型的配置长连接的方式: 

<Connector port="8080" protocol="HTTP/1.1" 
               connectionTimeout="20000" 
               redirectPort="8443" keepAliveTimeout="3600000" maxKeepAliveRequests="300"/> 
配置TOMCAT及httpClient的keepalive以高效利用长连接 - 网易杭研后台技术中心 - 网易杭研后台技术中心的博客 


发散一下:数据库连接池的连接也是有timeout属性的,比如mysql就有8小时问题 
show GLOBAL VARIABLES like 'wait_timeout'  查询mysql的timeout设置 
可以通过对数据库源配置checktimeout属性来解决,如c3p0 
<property name="checkoutTimeout" value="18000"/> 
来处理,也可以修改mysql的配置。 
其中: 
keepAliveTimeout:表示在下次请求过来之前,tomcat保持该连接多久。这就是说假如客户端不断有请求过来,且为超过过期时间,则该连接将一直保持。 
maxKeepAliveRequests:表示该连接最大支持的请求数。超过该请求数的连接也将被关闭(此时就会返回一个Connection: close头给客户端)。 

由于keeplive的不确定性,这对客户端高效的实现http请求带来了一定挑战。在httpClient中对连接的复用采用了多种机制同时保证,但是这需要和服务器端一起配合。如果服务器端和客户端在keepAlive相关的配置不匹配的话,轻则效率低下,重则伤筋动骨,具体有哪些需要注意的地方?下面一一道来。 

http连接池 

httpClient提供了连接池实现连接的复用,这是实现高效请求的基础,下面是给httpClient设置连接池的方法: 

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); 
//连接池最大生成连接数200 
cm.setMaxTotal(200); 
// 默认设置route最大连接数为20 
cm.setDefaultMaxPerRoute(20); 
// 指定专门的route,设置最大连接数为80 
HttpHost localhost = new HttpHost("locahost", 80); 
cm.setMaxPerRoute(new HttpRoute(localhost), 50); 
// 创建httpClient 
CloseableHttpClient httpClient = HttpClients.custom() 
        .setConnectionManager(cm) 
        .build(); 

有几个需要注意的地方: 
1.如果你的客户端连接的目标服务器只有一个,那么大可设置最大route连接数和最大连接池连接数相同,以便高效利用连接池中创建的连接。 
2.创建的httpClient对象是线程安全的,如果连接的目标服务器只有一个的话,创建一个全局对象即可。一个对象好比开了一个浏览器,多个线程无需每次请求时专门开一个浏览器,统一一个即可。 
3.如果httpClient对象不再使用,记得关闭,释放与服务器保持连接的socket,以便服务器更高效的释放资源。 

连接清理线程 

虽然连接池有了,但是由于http连接的特殊性(只有在通讯正在进行(block)时才能够对IO事件做出反应)。一旦连接被放回连接池后,我们无从知道该连接是否还是keepalive的,且此时也无法监控当前socket的状态(即服务器主动关闭了连接,但客户端没有通讯时是不知道当前连接的状态是怎样的)。怎么办呢?httpClient采用了一个折中的方案来检查连接的“状态”,就是由客户端自己通过配置去主动关闭其认为是失效的连接。具体方法如下: 

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); 
                    // Close expired connections 
                    connMgr.closeExpiredConnections(); 
                    // Optionally, close connections 
                    // that have been idle longer than 30 sec 
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS); 
                } 
            } 
        } catch (InterruptedException ex) { 
            // terminate 
        } 
    } 
   
    public void shutdown() { 
        shutdown = true; 
        synchronized (this) { 
            notifyAll(); 
        } 
    } 
   


我们新建了一个Thread,该Thread每隔5s进行一次清理,把认为是expired或空闲超过30s的连接给关闭掉。这里关闭空闲的连接好理解。问题是关闭expired的连接,什么样的连接才算是expired呢?刚才说过客户端是无法检测到对应的连接是否是alive的,故通过一个配置告诉httpClient每个连接大概什么时候会断掉,httpClient会认为过了该时间的连接就是expired的连接。默认情况下,httpClient认为只要连接上服务器的socket将永远不会断掉。显然这种假设过于乐观。 

keepalive策略 

为了使connMgr.closeExpiredConnections();起到作用,我们需要指定连接keep alive策略,来告诉httpClient,哪些连接大概什么时候会过期,你可以关闭他们。下面是一个设置keep alive 策略的例子: 

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(); 

这个例子指明在访问www.naughty-server.com和其他未知服务器时的keeplive为一个固定值,如果服务器返回keeplive过期时间,则通过服务器告诉客户端该连接大概什么时候过期(注意这不是一个标准的http协议,不是所有服务器都支持)。 

总结 

如果httpClient是访问单一的地址(如rpc请求),通过与服务器配置配合能够更高效的利用客户端的连接池,例如以tomcat为例,设置maxKeepAliveRequests为-1则可以使该连接可以请求无限次数,而设置keepAliveTimeout为一个较大的时间则可以让客户端更有效的利用连接池中的连接。凡事都有前提,如果目标tomcat即用于rpc调用,又用于服务正常连接请求的话,则上面优化反而会大量消耗服务器资源。这时可以讲这两类服务拆分成两个tomcat进行分别处理。另外,如果在服务器前面配有代理的话(apache,nginx),则还需视情况设置代理的keepAlive策略以提高利用率。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值