最近几天在测境碰到一个问题,httpclient 在使用线程池时, 间隔性的出现 NoHttpResponseException 异常。
httpclient org.apache.http.NoHttpResponseException: host:443 failed to respond
用了连接池很多年了, 一搜自己的博客, 竟然没做过一次整理和收藏, 其实大致原因也猜出个八九不离十, 秉承着严谨的态度😄, 还是百度了一下...大致总结出2个原因
1.当服务端由于负载过大等情况发生时,可能会导致在收到请求后无法处理(比如没有足够的线程资源),会直接丢弃链接而不进行处理。此时客户端就会报错:NoHttpResponseException。
解决建议: 重试
2.客户端与服务端建立的请求在服务端已经失效。(例如:服务端 springboot 内置 tomcat 默认 keepAliveTimeout :20s,客户端自定义 keepAliveTimeout :30s,客户端连接池中取出的空闲连接可能已经被服务端失效,再次从连接池拿该失效连接进行请求时,就会报错。)
解决建议:检查并关闭失效连接
问题依然解决, 解决过程中温习的知识还是需要记载下, 方便下次出现问题, 或者自己再次使用和有需要的小伙伴方便查找,
PoolingHttpClientConnectionManager pool = new PoolingHttpClientConnectionManager(socketFactoryRegistry);// pool max connectpool.setMaxTotal(maxTotal);// 设置最大路由pool.setDefaultMaxPerRoute(defaultMaxPerRoute);RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(connectionRequestTimeout).setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build();HttpClientUtil.closeableHttpClient = HttpClients.custom()// 设置连接池管理.setConnectionManager(pool)// 设置请求配置.setDefaultRequestConfig(requestConfig)//问题一解决方案:设置重试.setServiceUnavailableRetryStrategy(new DefaultServiceUnavailableRetryStrategy(3, 2000))//问题二解决方案:调整 keepAliveTimeout,这样无法复用长连接.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())//问题二解决方案:设置重试次数.setRetryHandler(new DefaultHttpRequestRetryHandler(3, false))//问题二解决方案:设置自动关闭过期链接.evictIdleConnections(30, TimeUnit.SECONDS).build();
对于问题二解决方案中evictIdleConnections方法的工作原理感兴趣的同学, 可以查看源码, 这里贴出部分代码, 供参考, 其实自己实现也一样
- 初始化变量 HttpClientBuilder.evictIdleConnections()
public final HttpClientBuilder evictIdleConnections(final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {this.evictIdleConnections = true;this.maxIdleTime = maxIdleTime;this.maxIdleTimeUnit = maxIdleTimeUnit;return this;}
- 构建逻辑 HttpClientBuilder.build()
if (evictExpiredConnections || evictIdleConnections) {final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(cm,maxIdleTime > 0 ? maxIdleTime : 10, maxIdleTimeUnit != null ? maxIdleTimeUnit : TimeUnit.SECONDS,maxIdleTime, maxIdleTimeUnit);closeablesCopy.add(new Closeable() {@Overridepublic void close() throws IOException {connectionEvictor.shutdown();try {connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);} catch (final InterruptedException interrupted) {Thread.currentThread().interrupt();}}});connectionEvictor.start();}
- 初始化链接处理线程 IdleConnectionEvictor
public IdleConnectionEvictor(final HttpClientConnectionManager connectionManager,final ThreadFactory threadFactory,final long sleepTime, final TimeUnit sleepTimeUnit,final long maxIdleTime, final TimeUnit maxIdleTimeUnit) {this.connectionManager = Args.notNull(connectionManager, "Connection manager");this.threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory();this.sleepTimeMs = sleepTimeUnit != null ? sleepTimeUnit.toMillis(sleepTime) : sleepTime;this.maxIdleTimeMs = maxIdleTimeUnit != null ? maxIdleTimeUnit.toMillis(maxIdleTime) : maxIdleTime;this.thread = this.threadFactory.newThread(new Runnable() {@Overridepublic void run() {try {while (!Thread.currentThread().isInterrupted()) {Thread.sleep(sleepTimeMs);connectionManager.closeExpiredConnections();if (maxIdleTimeMs > 0) {connectionManager.closeIdleConnections(maxIdleTimeMs, TimeUnit.MILLISECONDS);}}} catch (final Exception ex) {exception = ex;}}});}