【异常】Spring Ai Alibaba 流式输出卡住无响应的问题

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;
}
相关推荐
程序员黑豆4 小时前
Java包装类:基本类型与对象的桥梁
java·前端·ai编程
桦说编程6 小时前
并发编程中的等待-通知模式:从 wait/notify 到 Guava Monitor
后端
mldong7 小时前
"applicant" 契约:退回发起人的闭环设计
java·架构
月华路8 小时前
G1 垃圾回收:脏卡队列、记忆集与并发精化线程机制
java·开发语言
用户938515635078 小时前
工厂模式与 Nest.js 核心思想 —— 从蜜雪冰城到企业级架构
后端·设计模式·nestjs
用户938515635078 小时前
实战 Todo CRUD —— 从路由到异常,手写一个完整模块
后端·typescript·nestjs
gugucoding8 小时前
47. 【Java】Java内存模型(JMM)与可见性
java·开发语言
GetcharZp8 小时前
Qdrant 向量数据库 Golang 实战指南:从零构建高性能检索应用
后端
zww89491119 小时前
校园跑腿系统开发实战指南:从需求分析到上线部署全流程解析
java
hsfxuebao9 小时前
OpenSpec 完整使用流程笔记 (SDD)
后端