
翻译:https://blog.ycrash.io/faster-web-applications-with-http3/
让我们讨论 Java 在处理现代 Web 通信方面的改进。HTTP3(或 HTTP/3)旨在实现更快的 Web 通信,这是对当前流行的 HTTP2 和更老的 HTTP1 的改进。
Java 24 和 OpenJDK 通过 JEP 517 为 HTTP 客户端 API 引入了 HTTP/3。
HTTP3 是超文本传输协议的最新进展,用于在万维网上交换信息。HTTP/3 使用了与其前身相同的语义,如 请求/响应/会话/状态码,但对它们的编码方式不同,并且以不同的方式维护会话状态。
早期的 HTTP/2 和 HTTP/1 假设在服务器和客户端之下有一个有状态的网络连接(TCP/IP),这保证了数据包的可靠传输,并承诺数据包将按顺序排列(排队)。但这也导致了一个问题,如果队列中的一个数据包丢失,就会引起延迟和重新排队。
HTTP3 之所以比 HTTP2 更快,是因为它在 TLS 网络层中用更快的 QUIC 协议取代了较慢的 TCP 协议。
QUIC 发音为“KWIK”。它最初是在 Google 实验室设计的,截至目前,大多数浏览器,如 Chrome、Microsoft Edge、Firefox 和 Safari 都支持它。
QUIC 通过在两台终端之间建立多路复用连接来提高面向连接的网络应用的性能。它使用用户数据报协议(UDP),旨在取代传输层中的 TCP,适用于许多应用。
那些学过计算机网络的人会回忆起,UDP 曾经是更快的协议。然而,它并没有被广泛使用,因为它是无连接的,缺乏可靠性、错误检测和纠正以及流量控制。它没有交付顺序的保证。
UDP 协议已经有了许多改进/增强,现在它被认为适用于网络通信。
在 HTTP2 中,如果我们在一个 TCP 连接上打开多个数据流,并且其中一个数据包因某种原因延迟,那么所有后续的 TCP 数据包也会在队列中延迟。如果连接超时,它们就会丢失。
但在 QUIC 中,由于使用了改进的 UDP 协议,多个数据流的数据包可以独立地到达所有终端。因此,即使其中一个数据流中的一个数据包延迟,它也会通过其他数据流到达目的地。这里不讨论整合数据包的底层机制,因为它们太复杂了。
QUIC 高效地管理多路复用连接,使其对用户/浏览器透明。QUIC 还减少了网络延迟,并在每个方向上执行带宽估计以避免拥塞。
有几个 Java 实现的 QUIC 协议或 HTTP/3 可用,例如 KWIK、Quiche4j、Flupke、Jetty 等。
虽然大多数浏览器已经支持 HTTP/3,但一些 Web 服务器在撰写本文时仍不支持 HTTP3。
Jetty 是已经实现了 HTTP3 协议的客户端和服务器端的 Web 服务器之一,即 Jetty 服务器支持 HTTP/3。Jetty 对 HTTP/3 的实现涉及处理网络级客户端套接字和服务器套接字,这可能会使我们的代码冗长且难以理解。
所以我们使用 Kwik,一个围绕 QUIC 协议的包装器。我们也可以使用 Jetty(v12+),它提供 HTTP/3 支持。
我们无需通过客户端和服务器套接字处理 QUIC 协议的网络层实现,而是可以使用一个用纯 Java 构建的包装器。
KWIK 是一个用纯 Java 实现的 QUIC 协议(RFC 9000)的客户端和服务器。
Flupke 是一个运行在 Kwik 之上的 Java HTTP3 实现,它可以进一步简化事情。
让我们用传统的 HTTP/2 连接编写我们的第一个程序,并检查其性能。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
publicclass AnachronisticHttpBenchmark {
public static void main(String[] args) throws Exception {
List<String> urls = List.of( "https://reutersinstitute.politics.ox.ac.uk/digital-news-report/2024/dnr-executive-summary",
"https://www.nytimes.com/2024/12/26/briefing/the-year-in-news.html",
"https://www.heraldnet.com/news/the-top-10-most-read-herald-stories-of-2024", "https://www.cbsnews.com/news/top-news-headlines-of-2024-month-by-month",
"https://www.cfr.org/article/ten-most-significant-world-events-2024"
);
HttpClient client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .connectTimeout(Duration.ofSeconds(10))
.build();
for (String url : urls) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.GET()
.build();
long startTime = System.nanoTime();
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
long endTime = System.nanoTime();
long durationMillis = (endTime - startTime) / 1_000_000;
int responseSizeBytes = response.body().length;
System.out.printf("URL: %s%n", url);
System.out.printf("Status: %d%n", response.statusCode());
System.out.printf("Response Time: %d ms%n", durationMillis);
System.out.printf("Response Size: %d bytes%n", responseSizeBytes);
System.out.println("=".repeat(60));
}
}
}
接下来,让我们用 HTTP/3 客户端重复同样的操作,然后我们将测量两者的性能。
import tech.kwik.flupke.Http3Client;
import tech.kwik.flupke.Http3ClientBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.List;
publicclass Sample {
public static void main(String[] args) throws IOException, InterruptedException {
List<String> urls = List.of( "https://reutersinstitute.politics.ox.ac.uk/digital-news-report/2024/dnr-executive-summary", "https://www.nytimes.com/2024/12/26/briefing/the-year-in-news.html", "https://www.heraldnet.com/news/the-top-10-most-read-herald-stories-of-2024/","https://www.cbsnews.com/news/top-news-headlines-of-2024-month-by-month","https://www.cfr.org/article/ten-most-significant-world-events-2024"
)
try {
for (String url : urls) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("User-Agent", "Flupke http3 library")
.timeout(Duration.ofSeconds(1000))
.build();
// Easiest way to create a client with default configuration
HttpClient defaultClient = Http3Client.newHttpClient();
HttpClient client = ((Http3ClientBuilder) Http3Client.newBuilder())
.connectTimeout(Duration.ofSeconds(1000))
.build();
long start = System.currentTimeMillis();
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
long end = System.currentTimeMillis();
int responseSizeBytes = httpResponse.body().length();
reportResult(httpResponse, end - start);
}
}
catch (IOException e) {
System.err.println("Request failed: " + e.getMessage());
}
catch (InterruptedException e) {
System.err.println("Request interrupted: " + e.getMessage());
}
}
private static void reportResult(HttpResponse<String> httpResponse, long duration) throws IOException {
//System.out.println("Got HTTP response " + httpResponse); - to check the raw response of you prefer
System.out.printf("Status: %d%n", httpResponse.statusCode());
System.out.printf("Response Size: %d bytes%n", responseSizeBytes);
System.out.println("Request completed in " + duration + " ms");
System.out.println("=".repeat(60));
}
}
//long downloadSpeed = httpResponse.body().length() / duration;
虽然我们只使用了几个 URL,但当程序运行时,你可以看到它们的响应时间有明显差异。使用 HTTP/3 客户端的程序每次都能更快地获取响应。
图:响应时间对比
你可以在上方看到,LatestHTTPBenchmark 类几乎每次都能在更少的毫秒内获取响应。
这些只是几个网页请求。想象一下,当你用 HTTP/3 套接字编写整个 Web 应用时,性能提升会有多大。
如果你的服务器也支持 HTTP/3,性能提升和减少的网络延迟会更高。
你的结果可能会因你的网络速度和硬件设置而略有不同,但总体趋势应该与我们观察到的相似。
我们还使用 yc-script 捕获的 360 度快照和我们的 yCrash 工具分析检查了这两个类的网络性能。
使用简单 HTTP 连接的类使用了更多的 TCP/IP 连接,并且许多 TCP/IP 连接处于 WAIT 状态,因为一些数据包在队列中丢失了。
图:网络报告:简单 HTTP 连接
相比之下,使用 HTTP/3 的类创建了更少的 TCP/IP 连接,主要依赖于更快的 UDP 连接,并且只有一个线程处于 WAIT 状态。
图:网络报告:HTTP3 连接
来源:https://ycrash.io/yc-report-netStat.jsp?ou=VHlhRkI1RW1rWlhoeXNsb3o4TzdaUT09&de=host&app=yc&ts=2025-06-07T14-58-54
HTTP/3 不仅使用的网络资源更少,而且与更快的 UDP 网络连接一起工作。处于 WAIT 状态的连接数量更少。
这表明网络工作完成得更快,从而减少了网络延迟。