Java 11 HttpClient Http2 流太多错误

Java 11 HttpClient Http2 Too many streams Error

我正在使用 Java 11 的 HttpClient 到 post 对 HTTP2 服务器的请求。 HttpClient 对象创建为单例 Spring bean,如下所示。

@Bean
    public HttpClient getClient() {
                return HttpClient.newBuilder().version(Version.HTTP_2).executor(Executors.newFixedThreadPool(20)).followRedirects(Redirect.NORMAL)
                .connectTimeout(Duration.ofSeconds(20)).build();
    }

我正在使用 sendAsync 方法异步发送请求。

当我尝试连续访问服务器时,我在一定时间后收到错误消息“java.io.IOException:并发流过多”。我在 Client building 中使用 Fixed threadpool 试图克服这个错误,但它仍然给出同样的错误。

异常堆栈是..

java.util.concurrent.CompletionException: java.io.IOException: too many concurrent streams
    at java.base/java.util.concurrent.CompletableFuture.encodeRelay(CompletableFuture.java:367) ~[?:?]
    at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1108) ~[?:?]
    at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235) ~[?:?]
    at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:345) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0(MultiExchange.java:250) ~[java.net.http:?]
    at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1072) ~[?:?]
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506) ~[?:?]
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1705) ~[?:?]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) [?:?]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) [?:?]
    at java.base/java.lang.Thread.run(Thread.java:834) [?:?]
Caused by: java.io.IOException: too many concurrent streams
    at java.net.http/jdk.internal.net.http.Http2Connection.reserveStream(Http2Connection.java:440) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.Http2ClientImpl.getConnectionFor(Http2ClientImpl.java:103) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.ExchangeImpl.get(ExchangeImpl.java:88) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.Exchange.establishExchange(Exchange.java:293) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:425) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:330) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.Exchange.responseAsync(Exchange.java:322) ~[java.net.http:?]
    at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:304) ~[java.net.http:?]

有人可以帮我解决这个问题吗?

服务器为Tomcat9,默认最大并发流

When I try to hit the server continuously

服务器有一个 max_concurrent_streams 的设置,在初始建立 HTTP/2 连接期间与客户端通信。

如果你盲目地 "hit the server continuously" 使用 sendAsync 你没有等待之前的请求完成,最终你超过 max_concurrent_streams 值并收到上面的错误。

解决办法是并发发送的请求数小于max_concurrent_streams;之后,您只在前一个请求完成时发送一个新请求。 这可以很容易地在客户端使用 Semaphore 或类似的东西实现。

不幸的是,@sbordet 建议的 Semaphore 方法对我不起作用。我试过这个:

var semaphore = semaphores.computeIfAbsent(getRequestKey(request), k -> new Semaphore(MAX_CONCURRENT_REQUESTS_NUMBER));

CompletableFuture.runAsync(semaphore::acquireUninterruptibly, WAITING_POOL)
                .thenComposeAsync(ignored -> httpClient.sendAsync(request, responseBodyHandler), ASYNC_POOL)
                .whenComplete((response, e) -> semaphore.release());

无法保证在执行传递到下一个释放信号量的 CompletableFuture 时释放连接流。对我来说,这种方法在正常执行的情况下有效,但是如果有任何异常,似乎在调用 semaphore.release() 之后连接流可能会关闭。

最后,我用OkHttp结束了。它处理了这个问题(如果并发流的数量达到max_concurrent_streams,它只是等到一些流被释放)。它还处理 GOAWAY 帧。在 Java HttpClient 的情况下,我必须实现重试逻辑来处理这个问题,因为如果服务器发送 GOAWAY 帧,它只会抛出 IOException