一、问题描述
使用 HttpClient 并发调用http 接口,并发量稍微大一点就会报错
org.apache.http.NoHttpResponseException : 10.1.1.0:13001 failed to respond
二、排查过程
- 最开始怀疑是服务端连接过多,拒绝请求了,监控发现服务端并没有多少连接
- 找运维搭建了一个新环(只有我们请求服务端),发现只要并发大一点,必定会出现此错误
- 网上一通找,主要有这几个解决方法
a、添加自定义重试策略,在重试策略中判断异常如果是NoHttpResponseException,就重试
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setRetryHandler(new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
if (exception instanceof NoHttpResponseException) {
//NoHttpResponseException 异常重试
return true;
}
if (executionCount >= 3) {
return false;
}
return false;
}
b、减小validateAfterInactivity配置时间(这个配置是在从连接池获取连接时校验连接可用的间隔时间,默认2s)
PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
poolingHttpClientConnectionManager.setValidateAfterInactivity(1);
c、直接关闭连接重用,即
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionReuseStrategy(new DefaultConnectionReuseStrategy(){
@Override
public boolean keepAlive(HttpResponse response, HttpContext context) {
//不重用连接
return false;
}
});
- 但可惜,这三种都不能最终非常优雅的解决问题
- 重试虽然能解决这种问题,但是网络存在问题也可能会出现这种问题,可能请求已经发到服务器上了,只是最后响应没有写回,重试可能导致重复请求,存在风险,pass 掉
- 减小这个配置,虽然可以减低报错概率,但是并发非常大或者说请求很频繁的话,还是存在较低概率发生报错,pass 掉
- 直接不复用连接,那 httpclient 连接池的作用就不大了,影响性能,pass 掉(如果没有更好的办法就使用这个)
- 最后通过debug客户端和服务端的代码,发现服务端居然是短链接,每次返回响应就主动断掉连接(服务端使用的是 Netty Http服务)
- 有同事说反正服务端是短链接,那我们就使用第三种方案得了。但是我们不是只调用一个服务,其他好几个服务也是通过这个连接池调用的,可以说是一个工具类,不能直接这样改掉
- 最终采取了一个折中方案,采用第二、第三种方案结合的方式,解决此问题
- 自定义重用连接的策略
- 如果服务端的响应 header 带了 Connection=close,则不复用连接
- 如果服务端连接已不可用,则不复用连接(即调用第二种方式去校验连接是否可用)
- 其他情况复用连接
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setConnectionReuseStrategy(CustomClientConnectionReuseStrategy.INSTANCE);
public class CustomClientConnectionReuseStrategy extends DefaultConnectionReuseStrategy {
public static final CustomClientConnectionReuseStrategy INSTANCE = new CustomClientConnectionReuseStrategy();
@Override
public boolean keepAlive(final HttpResponse response, final HttpContext context) {
final HttpRequest request = (HttpRequest) context.getAttribute(HttpCoreContext.HTTP_REQUEST);
if (request != null) {
final Header[] connHeaders = request.getHeaders(HttpHeaders.CONNECTION);
if (connHeaders.length != 0) {
final TokenIterator ti = new BasicTokenIterator(new BasicHeaderIterator(connHeaders, null));
while (ti.hasNext()) {
final String token = ti.nextToken();
if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
//请求头自带 connection:close,不复用连接
return false;
}
}
}
}
final Object obj = context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
if (obj instanceof HttpConnection) {
//连接是否已经关闭或者失效,不复用连接
return !((HttpConnection) obj).isStale();
}
return super.keepAlive(response, context);
}
}
标签:10.1,respond,return,final,httpClientBuilder,报错,new,连接,服务端
From: https://blog.51cto.com/u_16206170/8215042