HttpClient与CloseableHttpClient

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

大家好,又见面了,我是你们的朋友全栈君。

前言

起因是最近做的一个历史遗留项目,需要加些新需求,在本机进行压测时,发现在并发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状态,参考下图

HttpClient与CloseableHttpClient

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状态的链接,其内部源码会开启一个定时任务去检测。

 

HttpClient与CloseableHttpClient

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

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/127097.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • (图文)最详细的XAMPP的安装及使用教程「建议收藏」

    (图文)最详细的XAMPP的安装及使用教程「建议收藏」XAMPP的安装及使用教程1、简介2、安装运行3、配置数据库XAMPP的安装及使用教程1、简介XAMPP(Apache+MySQL+PHP+PERL)是一个功能强大的建站集成软件包。这个软件包原来的名字是LAMPP,但是为了避免误解,最新的几个版本就改名为XAMPP了。它可以在Windows、Linux、Solaris、MacOSX…

    2022年7月27日
    7
  • django 菜鸟篇+进阶篇[通俗易懂]

    django 菜鸟篇+进阶篇[通俗易懂]django自带webserver,故django开发的项目可以独立的运行,也可以安置在apache(+mod_python)下运行djangowikidjango主页django源码和apihello,worddemodjango官方文档django的官网手册http://www.djangobook.com/en/2.0/;对应的中文翻译版本

    2022年9月8日
    0
  • Ping 命令详解[通俗易懂]

    简述:ICMP协议是“InternetControlMessagePtotocol”(因特网控制消息协议)的缩写。它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。ping(PacketInternetGroper),因特网包探索器,用于测试网络连接量的程序。Ping发送一个ICMP;回声请求消息给目的地并报告是否收到所希望的ICMPecho(ICMP回声应答)。它是用来检查网络是否通畅或者网络连接速度的命令ping命令通常用来作为网络可用性的检查。ping命

    2022年4月10日
    36
  • 海思Hi3798MV100机顶盒芯片介绍[通俗易懂]

    海思Hi3798MV100机顶盒芯片介绍[通俗易懂]Hi3798MV100是海思推出的专门针对OTT机顶盒市场的高性价比芯片方案。在码流兼容性、在线视频播放的流畅性、图像质量以及整机性能方面保持业界最好的用户体验。集成四核高性能处理器、内置NEON,其处理性能可以满足各种差异化的业务需求,支持Dolby和DTS音频处理。支持H.265、H.264、AVS+、MVC、MPEG2、MPEG4、VC-1、VP6、VP8等多种格式的高清视频解码和高性…

    2022年6月15日
    86
  • 回归的认识以及OLS回归[通俗易懂]

    回归的认识以及OLS回归[通俗易懂]回归分析是统计学的核心,其实是一个广义的概念,通常指那些用一个或多个预测变量(自变量或解释变量)来预测响应变量(因变量、校标变量或结果变量)的方法。回归分析可以用来挑选与响应变量相关的解释变量,可以描述两者的关系,也可以生成等式,通过解释变量来预测响应变量。回归分析可以解释的部分问题,举例:预测人在跑步机上锻炼时消耗的卡路里数。其响应变量就是消耗的卡路里数,预测变量可以很多,比如锻炼时间、目标心率的时间比、平均速度、年龄、性别和身体质量指数(BMI)。从理论上来说,回归分析可以帮助解释如下问题:

    2025年6月2日
    0
  • 01分布两点分布_两点分布和01分布的区别

    01分布两点分布_两点分布和01分布的区别#–*–coding:utf-8–*–importmatplotlib.pyplotasplt#随机变量x只能取0,1我们称X服从以P为参数的(0-1)分布或两点分布p=

    2022年8月5日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号