Java 11 HttpClient Http2 流过多错误

mnp*_*npr 7 concurrency stream multiplexing http2 java-11

我正在使用HttpClientJava 11 将请求发布到 HTTP2 服务器。HttpClient 对象被创建为一个 Singleton 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();
    }

I am using the sendAsync method to send the requests asynchronously.

When I try to hit the server continuously, I am receiving the error after certain time "java.io.IOException: too many concurrent streams". I used Fixed threadpool in the Client building to try to overcome this error, but it is still giving the same error.

The Exception stack is..

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$2(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:?]
Run Code Online (Sandbox Code Playgroud)

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

服务器是Tomcat9,它的最大并发流是默认的。

sbo*_*det 8

当我尝试连续访问服务器时

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

如果您盲目地“连续命中服务器” sendAsync,则不会等待先前的请求完成,最终您会超出该max_concurrent_streams值并收到上述错误。

解决方案是同时发送多个小于 的请求max_concurrent_streams;之后,您只有在前一个请求完成时才发送新请求。这可以使用 aSemaphore或类似的东西在客户端上轻松实现。


Rom*_*nko 6

不幸的是,@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());
Run Code Online (Sandbox Code Playgroud)

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

最后,我最终使用了OkHttp。它处理这个问题(如果并发流的数量达到 ,它只是等待一些流被释放max_concurrent_streams)。它还处理GOAWAY框架。对于 Java,HttpClient我必须实现重试逻辑来处理这个问题,因为IOException如果服务器发送GOAWAY帧,它就会抛出异常。

  • @Scot我只是以与Java中实现类似的方式实现它。详情请参阅`jdk.internal.net.http.Http2Connection#keyString`。 (2认同)
  • @Scot 问题是关于 HTTP 2 多路复用和 Java 客户端的。我很久以前就研究过这个问题,所以我不能确定,但​​我记得如果主机支持 HTTP 2 多路复用,Java HTTP 客户端仅使用一个连接。因此,每个“scheme:host:port”使用一个信号量与这种情况相关。 (2认同)