Spring Boot 3.4 集成阿里云 PAI-EAS 出现连接池耗尽问题的排查与修复
昨天凌晨两点,监控平台突然告警,生产环境的 AI 推理服务响应时间从正常的 200ms 飙升到 5000ms,随后大量请求直接超时。查看应用日志,满屏都是org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool。这个异常非常典型,通常意味着 HTTP 连接池被占满且无法释放。当时项目正在对接阿里云 PAI-EAS 模型在线服务,业务量激增触发了这个隐藏极深的资源泄漏问题。
这个问题之所以难排查,是因为它在低并发测试环境下完全不会复现。只有当 QPS 超过一定阈值,且部分请求因网络波动或模型推理耗时较长时,连接池才会迅速枯竭。官方文档虽然推荐直接使用RestTemplate或WebClient的默认配置,但在我们这种高并发、长连接的 AI 推理场景下,默认配置反而成了性能瓶颈。
环境准备
本次排查基于生产环境实际配置,确保读者可以复现同样的问题场景。
- JDK 版本: 21.0.2 LTS
- Spring Boot 版本: 3.4.0
- HTTP 客户端: Apache HttpClient 5.3.1 (Spring Boot 3.4 默认不再包含,需显式引入)
- 阿里云 SDK: alibabacloud-pai-eas 2.0.1
- 操作系统: Linux Kernel 5.15.0
在pom.xml中,除了常规的 Spring Boot Starter 外,必须显式引入 HttpClient 5 的依赖,否则 Spring Boot 3.4 可能会回退到旧版客户端或导致类加载冲突。
```xml
org.springframework.boot
spring-boot-starter-web
org.apache.httpcomponents.client5
httpclient5
5.3.1
```
核心步骤
1. 复现连接池耗尽场景
为了定位问题,首先需要在本地模拟高并发请求。使用 JMH 或简单的线程池模拟 200 个并发线程同时调用 PAI-EAS 的推理接口。默认情况下,Spring Boot 创建的RestTemplate使用的是SimpleClientHttpRequestFactory,它每次请求都会新建连接,或者使用连接池配置极小的HttpComponentsClientHttpRequestFactory。
在 200 并发下,默认连接池上限通常为 20,总连接数上限为 200。一旦有部分请求处理时间超过 5 秒,新请求就会因为拿不到连接而抛出ConnectionPoolTimeoutException。
2. 分析根因:生命周期不匹配
排查过程中发现,问题不仅仅在于连接数配置。更深层的原因是PoolingHttpClientConnectionManager的生命周期管理不当。很多开发者习惯在@Bean方法中直接 new 一个连接管理器,但这会导致连接池无法优雅关闭,且无法动态调整路由参数。
另一个被忽视的细节是 PAI-EAS 服务的 Keep-Alive 策略。模型推理服务通常倾向于保持长连接以减少握手开销,但如果后端客户端不主动回收空闲连接,空闲连接会一直占用池资源,直到超时。
3. 实施修复方案
修复的核心在于显式配置PoolingHttpClientConnectionManager,并设置合理的MaxConnTotal和MaxConnPerRoute。同时,必须开启连接回收机制,定期清理空闲连接。
```java
@Configuration
public class HttpClientConfig {
@Bean
public PoolingHttpClientConnectionManager connectionManager() {
PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
// 全局最大连接数,根据业务 QPS 和平均响应时间计算
manager.setMaxTotal(500);
// 单路由最大连接数,PAI-EAS 通常只有一个 endpoint
manager.setDefaultMaxPerRoute(200);
return manager;
}
@Bean
public CloseableHttpClient httpClient(PoolingHttpClientConnectionManager manager) {
return HttpClientBuilder.create()
.setConnectionManager(manager)
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(5, TimeUnit.SECONDS)
.setConnectionRequestTimeout(5, TimeUnit.SECONDS)
.setSocketTimeout(60, TimeUnit.SECONDS)
.build())
.build();
}
@Bean
public RestTemplate restTemplate(CloseableHttpClient httpClient) {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setReadTimeout(60000);
return new RestTemplate(factory);
}
}
```
此外,必须引入连接回收线程。这是官方文档中容易遗漏的部分。如果不启动回收线程,连接池中的空闲连接不会自动释放,导致高并发后连接数居高不下。
```java
@PostConstruct
public void init() {
// 每 30 秒回收一次空闲连接
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
connectionManager.closeIdleConnections(30, TimeUnit.SECONDS);
connectionManager.closeExpiredConnections();
}
}, 30000, 30000);
}
```
验证与常见问题
修复完成后,再次进行 500 并发压测。观察JVM的线程状态和网络连接数,连接池不再出现Timeout异常。平均响应时间稳定在 300ms 以内,P99 延迟控制在 1.2s 左右。
| 配置项 | 默认配置表现 | 优化后配置表现 | 说明 |
| :--- | :--- | :--- | :--- |
| 最大连接数 (MaxTotal) | 20 | 500 | 默认值过小,无法支撑高并发 |
| 单路由连接数 (MaxPerRoute) | 20 | 200 | 针对单一 AI 服务端点需调大 |
| 连接超时时间 (ConnectTimeout) | 默认 5s | 5s | 保持短超时,快速失败 |
| 空闲连接回收 | 无 | 每 30s 回收 | 防止连接泄漏和僵尸连接 |
常见问题中,还有一个容易被忽略的点:HttpClient实例必须是单例的。如果在每个 Controller 方法中都 new 一个HttpClient,会导致线程数爆炸和端口耗尽。务必确保CloseableHttpClient作为 Spring Bean 被管理。
另外,有开发者尝试通过增加MaxConnTotal到 10000 来解决超时,这个方案虽然官方推荐,但在我们场景下反而更糟。过大的连接数会导致 TCP 握手风暴,触发阿里云端限流,最终导致整体服务不可用。合理的数值应该基于QPS * 平均响应时间来估算。
总结
对接阿里云 PAI-EAS 这类 AI 推理服务时,不能简单套用 Web 服务的 HTTP 配置。模型推理的耗时特性要求客户端具备更稳健的连接管理能力。显式配置连接池、启用空闲连接回收、确保 HttpClient 单例化,是避免生产环境连接池耗尽的三道防线。默认配置往往是为了通用场景设计的,在高并发垂直场景下,必须根据实际业务指标进行调优。
#后端 #Java #SpringBoot #阿里云 #HttpClient
你在实际项目中有遇到类似问题吗?欢迎在评论区分享你的经验和解决方案。