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
相关产品推荐
相关产品推荐

