美文网首页计算机网络
HttpClient与CloseableHttpClient

HttpClient与CloseableHttpClient

作者: 骋_listen | 来源:发表于2019-01-02 19:21 被阅读0次

    前言

    起因是最近做的一个历史遗留项目,需要加些新需求,在本机进行压测时,发现在并发600的状态下跑一段时间后,就会开始偶现500的错误。可能是老项目用的人少(B2B的项目),实际部署后以前也没有人反馈过这个问题,大致跟踪了下日志,发现是系统在调用第三方服务出现异常,这种情况原因很多,需要仔细看异常堆栈打出来的Exception信息,将问题范围缩小并求证,这次抛出的是java.net.SocketException: Too many open files。表明服务器上开启了过多socket句柄,超上限了(一般是1024),这种情况下是无法建立新的网络连接的。

    排查

    经验丰富的程序员这个时候会调用一下netstat命令(压测不能间断),发现有大量的TCP链接处于ESTABLISHED状态,也有少部分CLOSE-WAIT状态的TCP链接。再继续走源码,remote调用部分因为代码过老,用的是org.apache.commons.httpclient.HttpClient,每次调用都会new一个新的实例进行链接。

    try {  
      client.executeMethod(method);  
      byte[] responseBody = null;  
        
      responseBody = method.getResponseBody();  
        
    } catch (HttpException e) {  
      // TODO Auto-generated catch block  
      e.printStackTrace();  
    } catch (IOException e) {  
      // TODO Auto-generated catch block  
      e.printStackTrace();  
    }finally{  
      method.releaseConnection();  
        
    }  
    

    咋一看好像没有什么问题,虽然这种方式每次进行remote调用有开销,但按道理每次用完了会将资源释放出来,目前的并发还不足以导致socket句柄不够用的情况。但实际上这样的处理,socket并没有真正的close,通过之前HTTP与TCP的keep-alive的文档所说,如果HttpClient不主动发起close,链接会维持一段时间,而该链接又没有进行复用,在维持的时间内,其他并发一进来,可能就会抛出句柄不够用的异常。
    甚至还有更严重的,TCP链接进入了CLOSE_WAIT状态,参考下图

    TCP-CLOSE四次握手 ,因为某些异常服务端发起FIN,请求端被动关闭进入CLOSE-WAIT,却又没有接受到最后一次握手信息,导致SOCKET一直这个状态(一般被动关闭会维持2个小时)

    处理方法

    HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true));
    

    进一步探索(RestTemplate与ClosableHttpClient)

    上面的做法相当于HttpClient每次用完就关闭,一定程度上规避了这个异常,但是每次new\close的流程对JVM的内存消耗很大,在一定程度上十分影响性能,这个时候需要引入连接池,我们可以看下ClosableHttpClient,一个最简单的创建方法:

    HttpClients.custom()
                    .evictExpiredConnections()
                    .evictIdleConnections(30, TimeUnit.SECONDS)
                    .build()
    

    ClosableHttpClient默认会创建一个大小为5的连接池(针对RPC调用不频繁的情况),端到端的链接可以复用,配置evict相关的两个方法,一方面用于处理类似CLOSE_WAIT状态的异常链接,一方面用于处理IDLE状态的链接,其内部源码会开启一个定时任务去检测。


    image.png

    Spring WebClient下封装了专门用于restful请求的RestTempate实际上内部就采用了ClosableHttpClient,对于有连接池的Client来说,最好使用单例模式,同时根据调用量配置合适的连接池大小以及配置各种超时时间等,不多做赘诉,下面给个例子:

    @Configuration
    public class RestClientConfiguration {
    
        /**
         * create ClosablehttpClient
         *
         * @return httpClient
         */
        @Bean
        public RestTemplate restTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    
            //https config
            TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
            SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
                    .loadTrustMaterial(null, acceptingTrustStrategy)
                    .build();
    
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext
                    ,null, null, NoopHostnameVerifier.INSTANCE);
    
            Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", csf)
                    .build();
    
            PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
            //最大连接数3000
            connectionManager.setMaxTotal(3000);
            //路由链接数400
            connectionManager.setDefaultMaxPerRoute(400);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setSocketTimeout(60000)
                    .setConnectTimeout(60000)
                    .setConnectionRequestTimeout(10000)
                    .build();
    
            HttpComponentsClientHttpRequestFactory requestFactory =
                    new HttpComponentsClientHttpRequestFactory();
    
            CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig)
                    .setConnectionManager(connectionManager)
                    .evictExpiredConnections()
                    .evictIdleConnections(30, TimeUnit.SECONDS)
                    .build();
            requestFactory.setHttpClient(httpClient);
            return new RestTemplate(requestFactory);
        }
    }
    

    相关文章

      网友评论

        本文标题:HttpClient与CloseableHttpClient

        本文链接:https://www.haomeiwen.com/subject/ifizlqtx.html