【异常】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;
}
相关推荐
pshdhx_albert7 小时前
AI agent实现打字机效果
java·http·ai编程
沉鱼.448 小时前
第十二届题目
java·前端·算法
努力的小郑9 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞9 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3569 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3569 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁10 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp10 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥10 小时前
多进程和多线程的特点和区别
java·开发语言·jvm
惜茶11 小时前
vue+SpringBoot(前后端交互)
java·vue.js·spring boot