暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

使用HttpClient的正确姿势

怕不立刻 2021-08-19
2901



HttpClient 是 Apache HttpComponents 下的子项目,用来提供高效支持 HTTP 协议的客户工具包。本文主要分享在一次使用HttpClient中遇到的问题,并延伸和拓展相关知识点,通俗易懂地介绍如何正确的使用HttpClient。



01

问题发现


在一次线上的并发量突然猛增,导致上游调用系统调用本项目大量超时。而项目本身并没有报错的日志,只发现项目Tomcat线程数占满现象。




02

错误定位


查看线程堆栈信息,发现大部分线程处于WAITING状态。这里复习一下线程的几种状态:

    RUNNABLE 线程运行中或I/O等待
    BLOCKED 线程在等待monitor锁(synchronized关键字)
    TIMED_WAITING 线程在等待唤醒,但设置了时限
    WAITING 线程在无限等待唤醒


    从线程的堆栈信息可以明显看出,线程在AQS的队列中等待被唤醒,也能看到线程的起始方法和类,这里只展示到 CloseableHttpClient.java


    我们基本可以知道线程都卡在了HttpClient里面,且HttpClient本身是有个连接池管理,在堆栈信息里还可以看到有个类:

    org.apache.http.impl.conn.PoolingHttpClientConnectionManager.java


    有两个很显眼的默认参数值分别是2和20,且这两个值分别对应HttpClientBuilder的两个配置参数,翻官方文档寻找字段解释如下:


    通俗的解释就是:

      maxConnTotal:最大连接数
      maxConnPerRoute:每个路由(域名)最大连接数


      也就是说如果没有配置这两个参数的话,默认每个域名最大只有2个请求连接数,所以导致项目的大量响应超时



      03

      延伸


      此时抛出新的问题:为什么没有错误日志?一直等待是否也不合理?下图是获接的一段源码以明显看出如超时时间null,就放到队列待。

      org.apache.http.pool.AbstractConnPool.java


      HttpClient 有一个请求配置类RequestConfig,有三个超时时间,官方字段解释如下:


      俗的解释就是

        connectionRequestTimeout:从连接池获取连接的超时时间
        connectTimeout:建立连接的超时时间
        socketTimeout:报文传输中,两个连续数据包之间的超时时间



        04

        拓展

        继续研究其他参数:

          automaticRetriesDisabled:自动重试开关,默认是不关闭
          retryHandler:重试策略,默认3次
          evictExpiredConnections:定时清理无效连接的开关,默认关闭,建议打开
          evictIdleConnections:定时清理闲置连接的开关,默认关闭,建议打开

          为什么建议开启清理无效连接和闲置的开关?

                  在HttpClient4.4版本之前,在从连接池中获取重用连接的时候会检查下是否过期,过期则清理。4.4版本开始,通过一个单独的线程来扫描连接池中的连接,发现有过期就会清理。因为每个client都会开一个线程,所以在配置HttpClient时必须配置为单例模式


          无效连接:因为某些连接建立后可能由于服务端单方面断开连接导致一个不可用的连接一直占用着资源


          闲置连接:HttpClient的连接复用

          先说 keep-alive 机制。每个 TCP 连接都要经过三次握手建立连接后才能发送数据,要经过四次挥手才能断开连接,如果每个 TCP 连接在服务端返回后都立马断开,则发起多个 HTTP 请求就要多次创建和断开 TCP,这在请求很多的情况下无疑是很耗性能的。如果在服务端返回后不立即断开 TCP 链接,而是复用这条连接进行下一次的 Http 请求,则可以省略了很多创建 断开 TCP 的开销,性能上无疑会有很大提升。


          虽然 keep-alive 省去很多不必要的握手/挥手操作,但由于连接长期存,如果没有请求的话也会浪费系统资源



          05

          总结



          • HttpClient自带连接池管理,使用时建议设置为单例
          • 使用HttpClient时必须根据实际情况配置最大连接数超时时间
          • 使用EntityUtils.toString()读取结果会自动关闭输入流,如果没有关闭输入流则需要使用releaseConnection()或者response.close()手动释放连接
          • HttpClient默认重试策略为3次可以通过设置automaticRetriesDisabled禁用重试

          文章转载自怕不立刻,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论