bigywater.tech
Java · 180

HttpClient:连接复用、异步请求和 HTTP/2

java.net.http.HttpClient 的连接复用、异步请求和 HTTP/2 到底怎么表现?这篇用自己起的 nginx(自签证书的 TLS + h2)与一个 JDK 自带的后端,用 nginx 日志里的连接编号数连接(HTTP/2 帧与多路复用在后面的篇目)。

1 vs 20
20 个请求:共用一个 HttpClient 与每次新建用的连接数
1 vs 50
50 个并发请求:HTTP/2 与 HTTP/1.1 用的连接数
HTTP_1_1
偏好 HTTP_2 访问明文端口时实际使用的版本

版本协商

实测 · 本机执行输出
  客户端偏好 HTTP_2 → https 端口:response.version() = HTTP_2,状态 200,body = ok
  客户端偏好 HTTP_2 → http 端口:response.version() = HTTP_1_1,状态 200,body = ok
  客户端偏好 HTTP_1_1 → https 端口:response.version() = HTTP_1_1,状态 200,body = ok

连接复用

实测 · 本机执行输出
  同一个 HttpClient 顺序发 20 个请求(HTTP/1.1):53 ms
  每个请求新建一个 HttpClient(HTTP/1.1):52 ms
  nginx 日志:shared 的 20 个请求用了 1 个连接;fresh 的 20 个请求用了 20 个连接

并发与多路复用

实测 · 本机执行输出
  HTTP_2 并发 sendAsync 50 个各耗时 100 ms 的请求:总耗时 124 ms
  HTTP_1_1 并发 sendAsync 50 个各耗时 100 ms 的请求:总耗时 119 ms
  nginx 日志:HTTP/2 的 50 个请求用了 1 个连接(协议 ['HTTP/2.0']);HTTP/1.1 的 50 个请求用了 50 个连接(协议 ['HTTP/1.1'])

超时与取消

实测 · 本机执行输出
  请求超时 500 ms 而服务端 3 秒才回:HttpTimeoutException: request timed out
  连接一个没人监听的端口:ConnectException
  sendAsync 之后 200 ms 调用 future.cancel(true):返回 true,isCancelled = false,isDone = true,isCompletedExceptionally = true
  之后 join() → CompletionException(cause = CancellationException)

分步演示

HttpClient
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12(java.net.http),nginx(自己的配置、非默认端口,自签名证书,JDK 客户端用 -Djdk.internal.httpclient.disableHostnameVerification=true 与信任所有证书的 SSLContext),后端用 JDK 自带的 com.sun.net.httpserver
    • 没有验证:h2c 明文升级(Upgrade: h2c)、连接池的空闲回收时间(jdk.httpclient.keepalive.timeout)、重定向与代理、WebSocket、响应体流式处理、跨网络时延下的耗时差别、取消请求时服务端的行为;本机回环上耗时差别很小
    京ICP备2026067502号-1京公网安备11010502063487号