fre*_*dev 0 java file-upload http2 java-http-client
上传多个文件时,使用多路复用 http2 功能应该会显着提高性能。
Java 有一个 httpclient,它原生支持 HTTP/2 协议,因此我尝试编写代码以供自己理解。
这项任务似乎并不像我最初想的那样容易,或者在另一方面,我似乎无法找到能够在上传中使用多路复用的服务器(如果存在)。
这是我写的代码,有人有想法吗?
HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
String url = "https://your-own-http2-server.com/incoming-files/%s";
Path basePath = Path.of("/path/to/directory/where/is/a/bunch/of/jpgs");
Function<Path, CompletableFuture<HttpResponse<String>>> handleFile = file -> {
String currentUrl = String.format(url, file.getFileName().toString());
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(currentUrl))
.header("Content-Type", "image/jpeg")
.PUT(HttpRequest.BodyPublishers.ofFile(file))
.build();
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
};
List<Path> files = Files.list(basePath).collect(toList());
files.parallelStream().map(handleFile).forEach(c -> {
try {
final HttpResponse<String> response = c.get();
System.out.println(response.statusCode());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException((e));
}
});
Run Code Online (Sandbox Code Playgroud)
上传多个文件时,使用多路复用 http2 功能应该会显着提高性能。
这是一个普遍错误的假设。
让我们放弃您有多个 HTTP/1.1 连接的情况,以便您可以并行上传。
然后我们有 1 个 TCP 连接,我们想将上传与 HTTP/1.1 和 HTTP/2 进行比较。
在 HTTP/1.1 中,请求会依次序列化,因此多次上传的结束时间取决于连接的带宽(忽略 TCP 慢启动)。
在 HTTP/2 中,请求将通过多路复用进行交错。但是,需要发送的数据是相同的,所以多次上传的结束时间又取决于连接的带宽。
在 HTTP/1.1 中,您将拥有upload1.start...upload1.end|upload2.start...upload2.end|upload3.start...upload3.end等。
在 HTTP/2 中,您将拥有 upload1.start|upload2.start|upload3.start.....upload3.end..upload1.end..upload2.end
结束时间将是相同的。
用HTTP / 2的问题是,你通常不被连接的带宽的限制,而是由HTTP / 2流量控制窗口,这通常是多,多,更小。
HTTP/2 规范默认 HTTP/2 流控制窗口为 65535 字节,这意味着每 65535 字节客户端必须停止发送数据,直到服务器确认这些字节。这可能需要一次往返,因此即使大文件上传的往返时间很小(例如 50 毫秒),您也可能多次为此往返支付费用,从而为您的上传增加秒数(例如,对于 6 MiB 上传,您可能需要为此支付 100次,即 5 秒)。
因此,使用大型 HTTP/2 流控制窗口配置服务器非常重要,尤其是当您的服务器用于文件上传时。服务器上较大的 HTTP/2 流控制窗口意味着服务器必须准备缓冲大量字节,这意味着主要处理文件上传的 HTTP/2 服务器将需要比 HTTP/1.1 服务器更多的内存。
使用更大的 HTTP/2 流控制窗口,服务器可能很聪明,并在客户端仍在上传时向客户端发送确认。
当客户端上传时,它会减少其“发送”窗口。通过从服务器接收确认,客户端放大“发送”窗口。
一个典型的不良交互是,指示客户端“发送”窗口值,从 1 MiB 开始:
[client send window]
1048576
client sends 262144 bytes
786432
client sends 262144 bytes
524288
client sends 262144 bytes
262144
client sends 262144 bytes
0
client cannot send
.
. (stalled)
.
client receives acknowledgment from server (524288 bytes)
524288
client sends 262144 bytes
262144
client sends 262144 bytes
0
client cannot send
.
. (stalled)
.
Run Code Online (Sandbox Code Playgroud)
一个好的交互应该是:
[client send window]
1048576
client sends 262144 bytes
786432
client sends 262144 bytes
524288
client sends 262144 bytes
262144
client receives acknowledgment from server (524288 bytes)
786432
client sends 262144 bytes
524288
client sends 262144 bytes
262144
client receives acknowledgment from server (524288 bytes)
786432
Run Code Online (Sandbox Code Playgroud)
正如您在良好的交互中看到的那样,服务器在客户端用完“发送”窗口之前确认客户端,因此客户端可以保持全速发送。
多路复用对于很多小请求确实有效,这就是浏览器用例:可以在 HTTP/2 中多路复用的许多小的 GET 请求(没有请求内容),比相应的 HTTP/1.1 请求更早到达服务器,并且因此将更早地提供服务并更早地返回浏览器。
对于大型请求,就像文件上传一样,HTTP/2 可以和 HTTP/1.1 一样高效,但如果服务器的默认配置使其性能远低于 HTTP/1.1 - HTTP/2,我不会感到惊讶将需要对服务器配置进行一些调整。
HTTP/2 流控制窗口也可能妨碍下载,因此通过 HTTP/2 从服务器下载大内容可能真的很慢(原因与上面解释的相同)。
浏览器通过告诉服务器有一个非常大的服务器“发送”窗口来避免这个问题 - Firefox 72 将其设置为每个连接 12 MiB,并且在确认服务器方面非常聪明,因此它不会停止下载。