Spring Ai Alibaba 流式输出卡住无响应的问题
关键点
RestClientCustomizer
WebClientCustomizer 重点 流式输出使用这个
java
// 定义全局WebClient
@Bean
public WebClientCustomizer webClientCustomizer() {
ConnectionProvider providerWeb = ConnectionProvider.builder("webClient-pool")
// 1️⃣ 最大连接数:≈ 并发 + 冗余
.maxConnections(500)
// 2️⃣ 等待连接的请求上限(防止雪崩)
.pendingAcquireMaxCount(200)
// 3️⃣ 等待连接的最长时间
.pendingAcquireTimeout(Duration.ofSeconds(30))
// 4️⃣ 空闲连接回收(SSE 结束后)
.maxIdleTime(Duration.ofSeconds(30))
// 5️⃣ 单连接最大存活时间(防"僵尸连接")
.maxLifeTime(Duration.ofMinutes(7))
// 6️⃣ 后台定期回收
.evictInBackground(Duration.ofSeconds(30))
// 7️⃣ 开启 metrics(强烈建议)
.metrics(true)
.build();
HttpClient httpClientWeb = HttpClient.create(providerWeb)
.protocol(HttpProtocol.HTTP11) // HTTP/1.1
// 连接超时:5秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
// 响应超时:60秒 这里不用控制 通过ReadTimeout来控制
// .responseTimeout(Duration.ofSeconds(60*10))
.doOnConnected(conn ->
//读超时(比如每 60 秒没收到数据就断开
conn.addHandlerLast(new ReadTimeoutHandler(60)) // 可选:更细粒度读超时(需导入 netty-handler)
)
// .wiretap(true); // 开启日志(需配置 logging.level.reactor.netty=DEBUG)
;
return builder -> builder.clientConnector(
new ReactorClientHttpConnector(httpClientWeb)
);
}
com.alibaba.cloud.ai.dashscope.api.DashScopeAgentApi
//初始化 webClient
private final RestClient restClient;
private final WebClient webClient;
java
public DashScopeAgentApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
ConnectionProvider providerRest = ConnectionProvider.builder("dashscope-restClient-pool")
// 1️⃣ 最大连接数:≈ 并发 + 冗余
.maxConnections(500)
// 2️⃣ 等待连接的请求上限(防止雪崩)
.pendingAcquireMaxCount(200)
// 3️⃣ 等待连接的最长时间
.pendingAcquireTimeout(Duration.ofSeconds(30))
// 4️⃣ 空闲连接回收(SSE 结束后)
.maxIdleTime(Duration.ofSeconds(30))
// 5️⃣ 单连接最大存活时间(防"僵尸连接")
.maxLifeTime(Duration.ofMinutes(7))
// 6️⃣ 后台定期回收
.evictInBackground(Duration.ofSeconds(30))
// 7️⃣ 开启 metrics(强烈建议)
.metrics(true)
.build();
HttpClient httpClientRest = HttpClient.create(providerRest)
.protocol(HttpProtocol.HTTP11) // HTTP/1.1
// 连接超时:5秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
// 响应超时:60秒 这里不用控制 通过ReadTimeout来控制
// .responseTimeout(Duration.ofSeconds(60*10))
.doOnConnected(conn ->
//读超时(比如每 60 秒没收到数据就断开
conn.addHandlerLast(new ReadTimeoutHandler(60)) // 可选:更细粒度读超时(需导入 netty-handler)
);
// .wiretap(true); // 开启日志(需配置 logging.level.reactor.netty=DEBUG)
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey))
.defaultStatusHandler(responseErrorHandler)
.requestFactory(new ReactorClientHttpRequestFactory(httpClientRest))
.build();
ConnectionProvider providerWeb = ConnectionProvider.builder("dashscope-webClient-pool")
// 1️⃣ 最大连接数:≈ 并发 + 冗余
.maxConnections(500)
// 2️⃣ 等待连接的请求上限(防止雪崩)
.pendingAcquireMaxCount(200)
// 3️⃣ 等待连接的最长时间
.pendingAcquireTimeout(Duration.ofSeconds(30))
// 4️⃣ 空闲连接回收(SSE 结束后)
.maxIdleTime(Duration.ofSeconds(30))
// 5️⃣ 单连接最大存活时间(防"僵尸连接")
.maxLifeTime(Duration.ofMinutes(7))
// 6️⃣ 后台定期回收
.evictInBackground(Duration.ofSeconds(30))
// 7️⃣ 开启 metrics(强烈建议)
.metrics(true)
.build();
HttpClient httpClientWeb = HttpClient.create(providerWeb)
.protocol(HttpProtocol.HTTP11) // HTTP/1.1
// 连接超时:5秒
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000)
// 响应超时:60秒 这里不用控制 通过ReadTimeout来控制
// .responseTimeout(Duration.ofSeconds(60*10))
.doOnConnected(conn ->
//读超时(比如每 60 秒没收到数据就断开
conn.addHandlerLast(new ReadTimeoutHandler(60)) // 可选:更细粒度读超时(需导入 netty-handler)
);
// .wiretap(true); // 开启日志(需配置 logging.level.reactor.netty=DEBUG)
this.webClient = webClientBuilder.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey, null, true))
.clientConnector(new ReactorClientHttpConnector(httpClientWeb))
.build();
}
单独定义
java
@Bean
public RestClientCustomizer restClientCustomizer() {
ClientHttpRequestFactory factory = clientHttpRequestFactory();
return builder -> builder.requestFactory(factory);
}
/**
* 配置ClientHttpRequestFactory
*
* @return ClientHttpRequestFactory实例
*/
private ClientHttpRequestFactory clientHttpRequestFactory() {
// 1. 配置连接层(TCP 连接、SSL 握手等)
ConnectionConfig connectionConfig = ConnectionConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(60)) // TCP + SSL 握手超时
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(1024); // Set max total connections
connectionManager.setDefaultMaxPerRoute(1024); // Set max connections per route
connectionManager.setDefaultConnectionConfig(connectionConfig);
// 显式定义超时
RequestConfig requestConfig =
RequestConfig.custom()
//大模型调用 需要预留充足时间
.setResponseTimeout(Timeout.ofSeconds(300)) // 注意:HttpClient 5 中是 responseTimeout(不是 socketTimeout)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig)
.disableAutomaticRetries()
.evictExpiredConnections()
.evictIdleConnections(TimeValue.ofSeconds(60)) // 每60秒清理空闲连接
.build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
clientHttpRequestFactory.setConnectTimeout(60_000);
clientHttpRequestFactory.setConnectionRequestTimeout(60_000);
clientHttpRequestFactory.setReadTimeout(Duration.ofSeconds(60));
return clientHttpRequestFactory;
}