You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot应用发送大量GET请求遇并发流过多异常求助

解决java.io.IOException: too many concurrent streams错误

这个错误是因为你使用的java.net.http.HttpClient默认采用HTTP/2协议,而HTTP/2对单个连接的并发流数量有严格限制(服务器端通常默认限制为100个),你一次性发起3500个异步请求,远超这个限制,导致服务器拒绝新的流请求。

可行解决方案

1. 控制并发请求数(推荐,平衡速度与合规性)

之前用Semaphore太慢可能是因为许可数设得太小,你可以调整许可数到服务器能承受的范围(比如50-200),同时确保请求完成后释放许可:

// 在类中定义Semaphore,调整许可数到合适值
private final Semaphore requestSemaphore = new Semaphore(100);

private void fetchAndSaveEventsForDateAsync(LocalDate date, boolean live) {
    requestSemaphore.acquireUninterruptibly(); // 获取请求许可
    String formattedDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
    String url = "HIDDEN ENDPOINT" + formattedDate;

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build();
    client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAcceptAsync(response -> {
                if (live) {
                    processEventsResponseLive(response);
                } else {
                    processEventsResponse(response);
                }
            })
            .exceptionally(ex -> {
                ex.printStackTrace();
                return null;
            })
            .whenComplete((unused, throwable) -> requestSemaphore.release()); // 完成后释放许可
}

2. 改用HTTP/1.1协议

HTTP/1.1通过连接池控制并发(默认每个主机最多5个连接,可调整),不会触发HTTP/2的并发流限制:

// 创建HttpClient时指定HTTP/1.1
HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_1_1)
        .followRedirects(HttpClient.Redirect.NORMAL)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

如果需要更高的并发连接数,可以通过系统属性调整:

// 设置每个主机的最大连接数
System.setProperty("jdk.httpclient.connectionPool.size", "50");

3. 分批异步处理

将3500个请求分成若干批次,每批完成后再发起下一批,避免瞬间请求量过大:

public void scrapeEventsForDateRange(LocalDate startDate, LocalDate endDate) {
    // 先收集所有需要请求的日期
    List<LocalDate> dates = new ArrayList<>();
    for (LocalDate date = endDate; !date.isBefore(startDate); date = date.minusDays(1)) {
        dates.add(date);
    }

    int batchSize = 100; // 每批处理100个请求
    for (int i = 0; i < dates.size(); i += batchSize) {
        // 构建当前批次的异步任务
        List<CompletableFuture<Void>> batchTasks = dates.subList(i, Math.min(i + batchSize, dates.size()))
                .stream()
                .map(date -> fetchAndSaveEventsForDateAsyncReturnFuture(date, false))
                .collect(Collectors.toList());
        // 等待当前批次所有任务完成
        CompletableFuture.allOf(batchTasks.toArray(new CompletableFuture[0])).join();
    }
}

// 修改方法返回CompletableFuture,方便批量处理
private CompletableFuture<Void> fetchAndSaveEventsForDateAsyncReturnFuture(LocalDate date, boolean live) {
    String formattedDate = date.format(DateTimeFormatter.ISO_LOCAL_DATE);
    String url = "HIDDEN ENDPOINT" + formattedDate;

    HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .build();
    return client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
            .thenApply(HttpResponse::body)
            .thenAcceptAsync(response -> {
                if (live) {
                    processEventsResponseLive(response);
                } else {
                    processEventsResponse(response);
                }
            })
            .exceptionally(ex -> {
                ex.printStackTrace();
                return null;
            });
}

注意事项

  • 无论哪种方案,都需要根据目标服务器的并发限制调整参数,避免触发反爬或服务熔断。
  • 如果使用Semaphore,确保在异常分支也能释放许可(上面的代码用whenComplete处理了这一点)。

内容的提问来源于stack exchange,提问作者Timo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 22:53:11