Spring应用RestTemplate.exchange()请求偶发10-180秒长延迟排查求助
碰到过好几个类似的偶发延迟问题,大多和RestTemplate默认的连接实现有关,我来帮你梳理可能的原因和解决办法:
默认连接实现无连接池,导致频繁新建连接阻塞
RestTemplate默认使用的是SimpleClientHttpRequestFactory,它不支持连接池——每次请求都会新建一个TCP连接。当系统并发量较高,或者遇到服务端连接释放缓慢时,新请求可能会因为等待系统分配文件句柄、完成TCP三次握手而被阻塞,这正是你看到的10-180秒偶发延迟的核心诱因之一。TCP TIME_WAIT连接堆积,耗尽可用端口
如果服务端主动关闭连接,客户端的TCP连接会进入TIME_WAIT状态(Linux默认保留60秒)。短时间内大量请求结束后,TIME_WAIT状态的连接会占满客户端的可用端口,新请求无法分配到端口,只能等待旧连接释放,这会直接导致数十秒的延迟,和你描述的时间范围完全吻合。DNS解析无缓存,偶发解析超时
默认的SimpleClientHttpRequestFactory没有配置DNS缓存,每次请求都可能重新发起DNS解析。如果DNS服务器偶尔响应缓慢、出现解析超时重试,就会导致请求发送前的长时间等待。
替换为支持连接池的请求工厂
推荐使用HttpComponentsClientHttpRequestFactory(基于Apache HttpClient),它自带成熟的连接池管理,能复用连接,彻底避免频繁创建TCP连接的开销。示例代码:// 初始化带连接池的RestTemplate RestTemplate restTemplate = new RestTemplate(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); // 根据业务调整连接池参数 connectionManager.setMaxTotal(200); // 全局最大连接数 connectionManager.setDefaultMaxPerRoute(50); // 单个域名的最大连接数 CloseableHttpClient httpClient = HttpClients.custom() .setConnectionManager(connectionManager) .build(); HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); restTemplate.setRequestFactory(factory);优化TCP参数缓解TIME_WAIT堆积
可以在客户端机器上调整系统TCP参数(需root权限):# 允许复用处于TIME_WAIT状态的端口 echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse # 缩短TIME_WAIT超时时间(建议30秒,谨慎调整) echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout也可以在HttpClient中配置连接的存活时间,主动淘汰闲置连接:
connectionManager.setValidateAfterInactivity(30000); // 闲置30秒后验证连接可用性配置DNS缓存避免重复解析
给HttpClient添加自定义DNS缓存,减少重复解析的开销:DnsResolver cachedDnsResolver = new DnsResolver() { private final LoadingCache<String, InetAddress[]> dnsCache = CacheBuilder.newBuilder() .expireAfterWrite(5, TimeUnit.MINUTES) // 缓存5分钟 .build(key -> InetAddress.getAllByName(key)); @Override public InetAddress[] resolve(String host) throws UnknownHostException { return dnsCache.getUnchecked(host); } }; CloseableHttpClient httpClient = HttpClients.custom() .setDnsResolver(cachedDnsResolver) .build();添加明确的超时配置
不管用哪种请求工厂,都要设置超时时间,避免无限等待:// 针对HttpComponentsClientHttpRequestFactory的超时配置 factory.setConnectTimeout(5000); // 连接超时5秒 factory.setConnectionRequestTimeout(5000); // 从连接池获取连接的超时5秒 factory.setReadTimeout(10000); // 读取响应超时10秒
- 检查客户端机器的TIME_WAIT连接数:执行
netstat -an | grep TIME_WAIT | wc -l,如果数量过万(比如超过2万),说明TIME_WAIT堆积是问题根源。 - 开启DEBUG级日志:设置
org.springframework.web.client.RestTemplate和org.apache.http的日志级别为DEBUG,查看延迟发生时的日志,定位是卡在连接建立、DNS解析还是其他阶段。 - 监控系统资源:用
lsof -p <你的应用进程ID> | wc -l查看文件句柄使用情况,如果接近系统限制(执行ulimit -n查看),说明资源不足导致连接阻塞。
内容的提问来源于stack exchange,提问作者Noble Shore

