Spring RestTemplate调用外部REST接口偶现Connection reset问题求解
问题排查与修复方案
根本原因
该偶发的Connection reset异常是典型的连接池内的空闲连接被服务端提前断开,客户端未感知到连接失效,继续复用过期连接导致的问题:
- 当前使用的Apache HttpClient连接池默认未启用空闲连接检测、未设置连接最大存活时间
- 第三方接口服务端通常会设置TCP连接的keepalive超时时间,多为30s/60s,超过该时长无流量就会主动断开连接,此时客户端连接池内的对应连接已经失效,但客户端未做有效性校验就直接复用发送请求,就会触发连接重置异常
- 异常后数分钟自动恢复是因为连接池内的失效连接被耗尽后,客户端会主动新建连接,流量就会自动恢复正常
排查步骤
- 确认第三方服务端的TCP keepalive超时时间、HTTP连接最大存活时间,拿到服务端主动断开连接的阈值
- 开启Apache HttpClient的调试日志,统计异常出现前对应连接的空闲时长,确认是否超过服务端的keepalive阈值
- 监控连接池的活跃连接、空闲连接数量变化,确认异常发生时是否有大量空闲连接被复用
修复方案
1. 配置连接池空闲连接驱逐策略、连接最大存活时间
修改PoolingHttpClientConnectionManager的Bean配置,增加最大连接存活时间,配置定时驱逐空闲连接:
@Bean @Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() { log.info("PoolingHttpClientConnectionManager Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT); // 设置连接最大存活时间,这里设为30s,建议比服务端的keepalive超时小10%左右 PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS); poolingHttpClientConnectionManager.setMaxTotal(maxTotalConnections); poolingHttpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 开启空闲连接驱逐,每5秒执行一次,清理空闲超过20秒的连接 poolingHttpClientConnectionManager.closeIdleConnections(20, TimeUnit.SECONDS); poolingHttpClientConnectionManager.setValidateAfterInactivity(5000); return poolingHttpClientConnectionManager; }
2. 配置HttpClient的重试策略
在构建CloseableHttpClient的时候增加幂等请求重试配置,遇到连接重置异常时自动重试:
@Bean(destroyMethod = "close") @Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) public CloseableHttpClient closeableHttpClient( @Qualifier(ConfigConstants.CONFIG_NAME_DEFAULT) PoolingHttpClientConnectionManager poolingHttpClientConnectionManager ) { log.info("CloseableHttpClient Build for {}", ConfigConstants.CONFIG_NAME_DEFAULT); // 配置重试策略,连接异常最多重试2次 HttpRequestRetryHandler retryHandler = (exception, executionCount, context) -> { if (executionCount > 2) { return false; } // 连接重置、连接超时这类连接异常可重试 if (exception instanceof SocketException || exception instanceof ConnectTimeoutException) { return true; } return false; }; return HttpClientBuilder.create() .setConnectionManager(poolingHttpClientConnectionManager) .setRetryHandler(retryHandler) .build(); }
3. 配置请求层面的连接校验
在HttpComponentsClientHttpRequestFactory中开启连接校验,复用连接前先确认连接是否有效:
requestFactory.setValidateAfterInactivity(5000);
4. 可选方案:禁用HTTP连接复用
如果业务请求量不大,也可以直接禁用连接复用,每次请求新建连接,就不会出现复用失效连接的问题,缺点是请求性能会有一定损耗:
return HttpClientBuilder.create() .setConnectionManager(poolingHttpClientConnectionManager) .setConnectionReuseStrategy((response, context) -> false) .build();
内容的提问来源于stack exchange,提问作者sanssan
相关产品推荐
相关产品推荐

