前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >网关使用 Apache HttpClient 连接池出现异常

网关使用 Apache HttpClient 连接池出现异常

作者头像
BUG弄潮儿
发布2022-06-30 16:53:59
8670
发布2022-06-30 16:53:59
举报
文章被收录于专栏:JAVA乐园

最近网关发版出现大量如下异常,而有如下文章:

代码语言:javascript
复制
org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool

两个主机建立网络连接是一个比较复杂的过程,涉及到多个数据包的交换。建立网络连接本身就很耗时间,而 Http 连接需要三次握手,开销就更大。但是可以直接使用已经建立好的 Http 连接,那么花费就比较小。耗时更短,从而提高访问的吞吐量。

传统的 HttpURLConnection 并不支持连接池,如果要实现连接池机制,那么需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,没有一定经验的程序员很难写好这块代码逻辑。

除了 HttpURLConnection,常用的Http Client 要数 Apache 的 HttpClient。一般情况下, HttpClient 已经能满足业务需求了;但是在网关这种高并发场景下,使用 HttpClient 进行大量的请求网络,还是需要用连接池才能提高网关的TPS,不然很容易成为网关的瓶颈。

Apache 的 HttpClient的早期版本,提供了PoolingClientConnectionManager、DefaultHttpClient 等类来实现 Http 连接池,但这些类在 4.3.x 版本之后大部分已过时。后续版本提供了PoolingHttpClientConnectionManager 等类进行 Http 连接池的实现。PoolingHttpClientConnectionManager 是一个 Http 连接池管理器,用来服务于多线程时并发获取连接的请求。每个路由(IP)将池化不大于 defaultMaxPerRoute 参数的连接。

pom.xml 文件引入依赖

代码语言:javascript
复制
<dependency>
     <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
</dependency>
<dependency>
      <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpcore</artifactId>
       <version>4.4.15</version>
</dependency>
<dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient-cache</artifactId>
      <version>4.5.13</version>
</dependency>

定义连接池主要参数

代码语言:javascript
复制
public class HttpPoolProperties {

    private Integer maxTotal;

    private Integer maxRoute;
    private Integer defaultMaxPerRoute;

    private String hostName;

    private Integer port;

    private Integer connectTimeout;

    private Integer connectionRequestTimeout;

    private Integer socketTimeout;

    private Integer validateAfterInactivity;

    // TODO 省略get set 方法
}
  • maxTotal:允许跨所有路线的最大连接数
  • defaultMaxPerRoute:所有路由默认最大连接数
  • maxPerRoute:指定某个路由的最大连接数

defaultMaxPerRoute 与 maxPerRoute 这两个参数存在优先级关系。具体可以参看 AbstractConnPool 类的如下方法的引用关系

代码语言:javascript
复制
private int getMax(T route) {
    Integer v = (Integer)this.maxPerRoute.get(route);
    return v != null ? v : this.defaultMaxPerRoute;
}
  • connectTimeout:多久等待与远程服务器抛出超时异常之前建立连接
  • socketTimeout:多久等待服务器抛出超时异常之前,各种消息响应
代码语言:javascript
复制
http://docs.oracle.com/javase/1.5.0/docs/api/java/net/SocketOptions.html#SO_TIMEOUT
  • connectionRequestTimeout:多久试图抛出异常之前,先从连接池中的连接时等待(连接池不会立即返回,如果所有的连接被检出)
  • staleConnectionCheckEnabled:可以在潜在的 IOExceptions 成本的性能有所提高被禁用
代码语言:javascript
复制
http://hc.apache.org/httpclient-3.x/performance.html#Stale_connection_check

获取 HttpClient 对象

代码语言:javascript
复制
public static CloseableHttpClient createHttpClient(HttpPoolProperties httpPoolProperties) {
        ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory> create().register("http", plainsf)
                .register("https", sslsf).build();
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
                registry);
        // 将最大连接数增加
        cm.setMaxTotal(httpPoolProperties.getMaxTotal());
        // 将每个路由基础的连接增加
        cm.setDefaultMaxPerRoute(httpPoolProperties.getDefaultMaxPerRoute());
        if(StringUtils.hasText(httpPoolProperties.getHostName()) && httpPoolProperties.getPort()!=null) {
            HttpHost httpHost = new HttpHost(httpPoolProperties.getHostName(), httpPoolProperties.getPort());
            // 将目标主机的最大连接数增加
            cm.setMaxPerRoute(new HttpRoute(httpHost), httpPoolProperties.getMaxRoute());
        }
        // 请求重试处理
        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                // 如果已经重试了5次,就放弃
                if (executionCount >= 5) {
                        return false;
                }
                  // 如果服务器丢掉了连接,那么就重试
                if (exception instanceof NoHttpResponseException) {
                        return true;
                }
                // 不要重试SSL握手异常
                if (exception instanceof SSLHandshakeException) {
                        return false;
                }
                // 超时
                if (exception instanceof InterruptedIOException) {
                        return false;
                }
                // 目标服务器不可达
                if (exception instanceof UnknownHostException) {
                        return false;
                }
                // 连接被拒绝
                if (exception instanceof ConnectTimeoutException) {
                        return false;
                }
                // SSL握手异常
                if (exception instanceof SSLException) {
                        return false;
                }


                HttpClientContext clientContext = HttpClientContext
                        .adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的,就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                        return true;
                }
                return false;
            }
        };
        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .setRetryHandler(httpRequestRetryHandler).build();
        return httpClient;
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-06-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BUG弄潮儿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档