使用 HttpClient 的连接泄漏状态 CLOSE_WAIT
Connections leaking with state CLOSE_WAIT with HttpClient
我们正在使用 JDK11 java.net.http
HTTP 客户端从 API 获取数据。在我们收到响应后,连接在我们的服务器中保持打开状态,TCP 状态为 CLOSE_WAIT
,这意味着客户端必须关闭连接。
来自RFC 793术语:
CLOSE-WAIT - represents waiting for a connection termination request
from the local user.
这是我们的客户端代码,它作为无状态 REST API 在 Java 12 上的 WildFly 16 运行 上运行。我们不明白为什么会这样。
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
public class SocketSandbox {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
try (var listener = new ServerSocket(59090)) {
System.out.println("Server is running...");
while (true) {
try (var socket = listener.accept()) {
HttpRequest request = HttpRequest
.newBuilder(URI.create("<remote_URL>"))
.header("content-type", "application/json").build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
var out = new PrintWriter(socket.getOutputStream(), true);
out.println(String.format("Response HTTP status: %s", response.statusCode()));
}
}
}
}
}
我们得到“状态代码”,表示已处理 http 响应。
使用相同的代码调用其他端点时连接正常。这似乎是我们正在调用的远程 API 的一个特殊问题,但我们仍然不明白为什么 Java HTTP 客户端保持连接打开。
我们尝试了 Windows 和 Linux 机器,甚至尝试了 WildfFly 之外的独立机器,但结果相同。在每个请求之后,即使是从我们的无状态客户端执行并收到响应,每个请求都保留为 CLOSE_WAIT
并且永远不会关闭。
如果我们关闭 Java 进程,连接将会消失。
HTTP客户端发送的Headers:
connection: 'Upgrade, HTTP2-Settings','content-length': '0',
host: 'localhost:3000', 'http2-settings': 'AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA',
upgrade: 'h2c',
user-agent': 'Java-http-client/12'
服务器 returns 响应 header:Connection: close
更新 (1)
我们尝试 fine-tune 实现中的池参数 class jdk.internal.net.http.ConnectionPool
。
没有解决问题
System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");
更新 (2)
使用 Apache HTTP 连接会在 CLOSE_WAIT 状态下停留大约 90 秒,但在那之后它可以连接。
调用方法HttpGet.releaseConnection()
强制立即关闭连接。
HttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet("https://<placeholderdomain>/api/incidents/list");
get.addHeader("content-type", "application/json");
HttpResponse response = client.execute(get);
// This right here did the trick
get.releaseConnection();
return response.getStatusLine().getStatusCode();
并且使用 OkHttp 客户端,它开箱即用,没有连接卡住。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://<placeholderdomain>/grb/sb/incidentes/listar")
.header("content-type", "application/json").build();
Response response = client.newCall(request).execute();
return response.body().string();
我们仍在努力寻找如何让它在 java-http-client 中工作,这样我们就不必重写代码。
我不建议为每个新请求创建一个新客户端。这违背了 HTTP/2 允许在单个连接上多路复用请求的目的。
第二个就是两个属性:
System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");
仅适用于 HTTP/1.1 个连接,不适用于 HTTP/2。还要注意这些属性只在 class 加载时读取一次。所以在之后设置它们
java.net.http
classes 已加载将无效。
最后,在释放 HttpClient
之后可能需要一些时间才能关闭所有保持活动的连接 - 这样做的内部机制基本上依赖于 GC - 这对短暂的 HttpClients 不是很友好.
已提交并确认为实施中的错误。
https://bugs.openjdk.java.net/browse/JDK-8221395
更新
检查 JIRA 问题,它已在 JDK 13 中修复,并已反向移植到 11.0.6。 (不确定 12 个)
我们正在使用 JDK11 java.net.http
HTTP 客户端从 API 获取数据。在我们收到响应后,连接在我们的服务器中保持打开状态,TCP 状态为 CLOSE_WAIT
,这意味着客户端必须关闭连接。
来自RFC 793术语:
CLOSE-WAIT - represents waiting for a connection termination request from the local user.
这是我们的客户端代码,它作为无状态 REST API 在 Java 12 上的 WildFly 16 运行 上运行。我们不明白为什么会这样。
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
public class SocketSandbox {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder().version(Version.HTTP_1_1).build();
try (var listener = new ServerSocket(59090)) {
System.out.println("Server is running...");
while (true) {
try (var socket = listener.accept()) {
HttpRequest request = HttpRequest
.newBuilder(URI.create("<remote_URL>"))
.header("content-type", "application/json").build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
var out = new PrintWriter(socket.getOutputStream(), true);
out.println(String.format("Response HTTP status: %s", response.statusCode()));
}
}
}
}
}
我们得到“状态代码”,表示已处理 http 响应。
使用相同的代码调用其他端点时连接正常。这似乎是我们正在调用的远程 API 的一个特殊问题,但我们仍然不明白为什么 Java HTTP 客户端保持连接打开。
我们尝试了 Windows 和 Linux 机器,甚至尝试了 WildfFly 之外的独立机器,但结果相同。在每个请求之后,即使是从我们的无状态客户端执行并收到响应,每个请求都保留为 CLOSE_WAIT
并且永远不会关闭。
如果我们关闭 Java 进程,连接将会消失。
Headers:
connection: 'Upgrade, HTTP2-Settings','content-length': '0',
host: 'localhost:3000', 'http2-settings': 'AAEAAEAAAAIAAAABAAMAAABkAAQBAAAAAAUAAEAA',
upgrade: 'h2c',
user-agent': 'Java-http-client/12'
服务器 returns 响应 header:Connection: close
更新 (1)
我们尝试 fine-tune 实现中的池参数 class jdk.internal.net.http.ConnectionPool
。
没有解决问题
System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");
更新 (2)
使用 Apache HTTP 连接会在 CLOSE_WAIT 状态下停留大约 90 秒,但在那之后它可以连接。
调用方法HttpGet.releaseConnection()
强制立即关闭连接。
HttpClient client = HttpClients.createDefault();
HttpGet get = new HttpGet("https://<placeholderdomain>/api/incidents/list");
get.addHeader("content-type", "application/json");
HttpResponse response = client.execute(get);
// This right here did the trick
get.releaseConnection();
return response.getStatusLine().getStatusCode();
并且使用 OkHttp 客户端,它开箱即用,没有连接卡住。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://<placeholderdomain>/grb/sb/incidentes/listar")
.header("content-type", "application/json").build();
Response response = client.newCall(request).execute();
return response.body().string();
我们仍在努力寻找如何让它在 java-http-client 中工作,这样我们就不必重写代码。
我不建议为每个新请求创建一个新客户端。这违背了 HTTP/2 允许在单个连接上多路复用请求的目的。
第二个就是两个属性:
System.setProperty("jdk.httpclient.keepalive.timeout", "5"); // seconds
System.setProperty("jdk.httpclient.connectionPoolSize", "1");
仅适用于 HTTP/1.1 个连接,不适用于 HTTP/2。还要注意这些属性只在 class 加载时读取一次。所以在之后设置它们
java.net.http
classes 已加载将无效。
最后,在释放 HttpClient
之后可能需要一些时间才能关闭所有保持活动的连接 - 这样做的内部机制基本上依赖于 GC - 这对短暂的 HttpClients 不是很友好.
已提交并确认为实施中的错误。
https://bugs.openjdk.java.net/browse/JDK-8221395
更新
检查 JIRA 问题,它已在 JDK 13 中修复,并已反向移植到 11.0.6。 (不确定 12 个)