一、工具简介
HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。
二、工具结构
HttpClient使用了责任链模式,所有Executor都实现了ClientExecChain接口的execute()方法
2.1 执行链介绍
2.2 连接池程池交互流程
2.3 连接的等待与唤醒
唤醒连接:当有连接释放时就会触发唤醒
- httpclient做等待和唤醒使用的ReentrantLock且配置为是非公平锁
- 放入等待队列时调用condition.await()/awaitUntil(deadline)方法
- 唤醒时调用condition.signalAll()方法
三、参数配置
1.HttpClients参数配置
1.1 配置连接策略(KeepAliveStrategy)
默认连接策略DefaultConnectionKeepAliveStrategy.INSTANCE
默认的连接策略返回header中的Keep-Alive:timeout乘以1000。下图为连接保持60秒
如果没有返回则是走默认时长(ConnectionManager中的timeToLive配置参数)
MainClientExec会设置过期时间
HttpClientBuilder初始化会有空闲连接池过期策略处理(IdleConnectionEvictor),此策略单独启线程循环执行检查过期连接并关闭。
1.2 配置连接管理类(ConnectionManager)
ConnectionManager有哪些?
BasicHttpClientConnectionManager是一次只能有一个线程使用的单个连接
PoolingHttpClientConnectionManager支持多个连接,连接交由连接池管理。(默认配置)
1.3 开启httpClient过期连接清理处理类(IdleConnectionEvictor)
evictExpiredConnections : 开启
evictIdleConnections:
maxIdleTime:长连接在空闲连接池中的保留时间。此值配置后除了链接本身的过期时间到了会清理,链接闲置超出此配置的时间到了也会被清理,对连接策略有一定冲突。
maxIdleTimeUnit:单位
1.4 重试策略(RetryHandler)
默认策略为DefaultHttpRequestRetryHandler.INSTANCE
disableAutomaticRetries()关闭重试策略
setRetryHandler(HttpRequestRetryHandler)设置自己的重试逻辑
1.5 设置默认请求配置(RequestConfig)
1.6 设置拦截器addInterceptorFirst\addInterceptorLast
LinkedList<HttpRequestInterceptor>:requestFirst、requestLast
LinkedList<HttpResponseInterceptor>:responseFirst、responseLast
可对请求及响应设置拦截器
执行顺序:requestFirst/responseFirst -> httpClient默认拦截器 -> requestLast/responseLast
2.PoolingHttpClientConnectionManager参数配置
构造参数:
- Registry<ConnectionSocketFactory> :协议注册,通常情况注册http、https协议。需要注意默认的https请求对安全证书有要求,如果系统安全需求不高的情况可以对所有ssl信任。
- HttpConnectionFactory:提供管理和创建HTTP服务器的连接方式,默认ManagedHttpClientConnectionFactory。无特殊需求无需更改
- SchemePortResolver:根据请求地址获取端口,默认DefaultSchemePortResolver可根据协议返回响应端口。无特殊需求无需更改
- DnsResolver:将主机名解析成ip地址,方便建立连接。
- timeToLive/timeUnit:连接存活时间(连接策略优先于此配置)
其它设置:
- MaxTotal:总连接池大小。当并发请求数大于设置值时,放入等待队列中。
- DefaultMaxPerRoute:默认单地址连接池大小,当某地址(如http://www.abc.com)并发请求数大于设置值时,放入等待队列
- MaxPerRoute:与DefaultMaxPerRoute作用一致,对某个连接的池子数进行单独配置
3.设置默认请求配置(RequestConfig)
常用:
- ConnectTimeout:请求超时时间
- SocketTimeout:响应超时时间
- ConnectionRequestTimeout:连接池等待时间
- setExpectContinueEnabled:客户端会先发送一个带有"Expect: 100-continue"头部的请求建立握手,可以避免请求失败却发送大量信息
不常用:
- setRedirectsEnabled:是否允许HTTP请求自动处理重定向
- setLocalAddress:指定本地请求网络。(InetAddress.getByAddress(new byte[]{192, 168, 0, 100}))
- setCookieSpec:Cookie策略。默认为CookieSpecs.DEFAULT
- setCircularRedirectsAllowed:无限重定向开关,默认关闭,谨慎打开
- setMaxRedirects:无限重定向次数
- setAuthenticationEnabled:是否启用HTTP身份验证功能
- setContentCompressionEnabled:内容压缩,可以根据请求头中的Accept-Encoding字段来决定是否启用内容压缩。
- setNormalizeUri:uri规范化。示例:http://abc.com/../xxx会改为http://abc.com/xxx
四、httpClient耗时问题及分析
1.问题现象
httpClient请求耗时从日志时间看超过10分钟
2.排查流程
- 通常情况请求耗时过高一般是因为:
- 请求超时(也就是三次握手)慢或不可用 - ConnectTimeout
- 响应超时与服务器建立连接但因网络或服务端响应慢导致超时 - SocketTimeout
- 大报文耗时:受制于网络传输时间、带宽等条件限制,此情景不会触发超时异常。
- 上述情况可以分别对请求、响应超时时间进行配置来解决问题。实际项目情况已经配置过超时时间。
- 为检验配置是否生效对http工具进行上述两种超时场景进行模拟
- 请求超时:采用链接一个不可到达的IP地址http://10.253.64.2:8080/test
- 响应超时:服务端睡眠时间>httpClient工具配置响应超时时间
- 经检验配置生效,均在规定时间内抛出超时异常
- 配置正常未检测出耗时长的问题超出认知范围
- 回顾场景,通过图查看问题时间段的并发现象。下图为应用一天顶峰时tps\tp90\tp999
- 根据图可知单机房最高并发接近90,m6机房3个机器,单机器并发30,tp90:5-15秒之间,tp999则不稳定最高可到10分
- 猜测高并发+高耗时可能是导致发生超长耗时的原因
- 开始模拟场景
- 每秒30并发
- 服务端响应时间全部为10秒才响应
- 总连接最大连接数为30(实际项目设置的是150)
- 单地址连接最大连接数为30(实际项目设置的是150)
- 压测后的结果出现先请求的应用却后响应的现象。下图是当请求+响应耗时极限时间20秒时,却出现40秒响应的现象(发送body内容很少不可能是因为信息传输增加耗时)
- 经排查源码发现当连接数打满时会放入等待队列中,等待队列当没设置超时时间则会一直等待。
- 等待队列释放条件当有请求释放连接时,对列头部的连接则会释放去抢夺连接。
- 开始极限压测,问题暴露就更加明显
- 排查源码MultiThreadedHttpConnectionManager.doGetConnection。common包httpClient获取连接流程
- 值得一提的是这里的等待用的是Object.wait,唤醒是中断线程实现(waitingThread.thread.interrupt())
- 至此问题原因定位90%
3.问题解决
- 尽量避免连接池打满:增加单连接、总连接池的连接数,避免触碰极限值。注意:系统承受能力、下游承受能力
- 对等待连接池时间进行控制:配置连接池等待时间
效果:
tp90:
tp:999
- 至此问题原因定位100%