如何从 HttpClient 设置 TCP keep Alive?
How to set TCP keep Alive from HttpClient?
我的 Java 驻留在 AWS 私有子网中的应用程序通过 AWS Nat 网关连接到 http 服务器。我正在通过 HttpClient
向 HTTP 服务器调用 POST 请求。该请求将需要 10 多分钟才能完成。我已经配置了 1 小时的套接字超时和连接超时,因为这是一个后台任务。但是中间 AWS NAT 网关将在 300 秒 [5 分钟] 后发回 RST 数据包并导致连接重置,我无法增加 NAT 网关超时。所以我需要从我的应用程序端处理这个问题。
我的策略是使用 TCP 保持活动时间,它会每 240 秒发送一个数据包以保持连接处于活动状态。我已经配置了这个
如下
CloseableHttpClient httpClient = HttpClients.createDefault()
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3600000); //connection Timeout
HttpConnectionParams.setSoTimeout(params, 3600000); // Socket Time out
HttpConnectionParams.setSoKeepalive(params, true); //Enable Socket level keep alive time
然后通过execute方法调用post请求
HttpPost post = new HttpPost("http://url");
HttpResponse response = httpClient.execute(post);
由于我使用的是 Linux 系统,因此我已使用以下 sysctl 值配置服务器:
sysctl -w net.ipv4.tcp_keepalive_time=240
sysctl -w net.ipv4.tcp_keepalive_intvl=240
sysctl -w net.ipv4.tcp_keepalive_probes=10
但是在执行程序时,没有启用保持活动状态,连接失败。
我已经使用 netstat -o 选项检查了这一点,如下所示,keep alive 已关闭
tcp 0 0 192.168.1.141:43770 public_ip:80 ESTABLISHED 18134/java off (0.00/0/0)
有什么方法可以使用 httpclient 从 java 代码设置 TCP keep alive。我还可以看到 HttpConnectionParams
已被弃用。但是我找不到任何新的 class 可以设置 keep alive
我找到了解决问题的方法。奇怪的情况是我无法在 httpclient 中使用一些构建器 class 来传递 socket keep alive 。我在问题中指定的一种方法是使用 HttpConnectionParams,如下所示,但这不起作用,现在不推荐使用此 class。
HttpParams params = httpClient.getParams();
HttpConnectionParams.setSoKeepalive(params, true);
因此,在检查 apache http 文档时,我可以看到现在连接参数通过 RequestConfig class 传递给了 httpclient。此 class 的构建者提供了设置 connection_time_out 和 socket_time_out 的解决方案。但是检查这个源代码我看不到启用 SocketKeepAlive 的选项,这正是我们想要的。因此唯一的解决方案是使用 SocketBuilder class 直接创建 Socket 并将其传递给 HttpClientBuilder。
以下是工作代码
SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(3600000).build(); //We need to set socket keep alive
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3600000).build();
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).
setDefaultSocketConfig(socketConfig).build();
HttpPost post = new HttpPost(url.toString());
HttpResponse response = httpClient.execute(post);
在上面执行时,我可以看到根据我在 linux 内核
中设置的 sysctl 值,在套接字中正确设置了 keep alive
tcp 0 0 localip:48314 public_ip:443 ESTABLISHED 14863/java keepalive (234.11/0/0)
如果有人有更好的解决方案来从 Requestconfig class 或任何其他高级构建器 class 启用 Socket Keep alive,我愿意接受建议。
保持 HTTP 连接打开但长时间不活动是一个糟糕的设计选择。 HTTP 是一种请求-响应协议,意味着请求和响应很快。
保持连接打开会保持资源。从服务器(以及网络防火墙和路由器)的角度来看,打开连接并开始请求(在您的情况下为 POST)但长时间不发送任何字节的客户端与客户端无法区分将永远不会 发送任何更多数据,因为它是错误的或恶意的(进行 DOS 攻击)。服务器(和网络硬件)正确地得出结论,正确的做法是关闭连接并回收用于它的资源。您正试图与出于充分理由而发生的正确行为作斗争。即使您设法解决 TCP 关闭问题,您也会发现其他问题,例如 HTTP 服务器超时和数据库超时。
您应该重新考虑两个组件之间的通信设计。也就是说,这看起来像一个 XY 问题。您可能会考虑
- 在 开始 POST 之前,让客户端等到它完成上传以执行 。
- 将上传分成更小、更频繁的上传。
- 使用 HTTP 以外的协议。
上面使用 Socket 的方法在 AWS 网络负载均衡器超时以下重置 tcp_keepalive_intvl 值时效果很好。使用两者,重置允许 java 小时以上连接的 NLB tcp 空闲超时。
有时,如果配置被覆盖,配置不采取 effect.My buildClient 中的 setDefaultSocketConfig 初始修改没有采取 effect.Because 它被 getConnectionManager() 覆盖
public CloseableHttpClient buildClient() throws Exception {
HttpClientBuilder builder = HttpClientBuilder.create()
.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).build()) // did not work
.setConnectionManager(getConnectionManager())
.setRetryHandler(getRequestRetryHandler())
.setConnectionReuseStrategy(getConnectionReuseStrategy())
.setDefaultConnectionConfig(getConnectionConfig())
.setDefaultRequestConfig(getRequestConfig())
.setDefaultHeaders(getDefaultHeaders())
.setDefaultCredentialsProvider(getDefaultCredentialsProvider())
.disableContentCompression() // gzip is not needed. Use lz4 when compress=1
.setDefaultCookieStore(cookieStoreProvider.getCookieStore(properties))
.disableRedirectHandling();
String clientName = properties != null ? properties.getClientName() : null;
if (!Utils.isNullOrEmptyString(clientName)) {
builder.setUserAgent(clientName);
}
return builder.build();
然后我将配置移动到 getConnectionManager(),它工作了。
private PoolingHttpClientConnectionManager getConnectionManager()
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RegistryBuilder<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory());
if (properties.getSsl()) {
HostnameVerifier verifier = "strict".equals(properties.getSslMode()) ? SSLConnectionSocketFactory.getDefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE;
registry.register("https", new SSLConnectionSocketFactory(getSSLContext(), verifier));
}
//noinspection resource
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
registry.build(),
null,
null,
new IpVersionPriorityResolver(),
properties.getTimeToLiveMillis(),
TimeUnit.MILLISECONDS
);
connectionManager.setDefaultMaxPerRoute(properties.getDefaultMaxPerRoute());
connectionManager.setMaxTotal(properties.getMaxTotal());
connectionManager.setDefaultConnectionConfig(getConnectionConfig());
connectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).build());
return connectionManager;
}
我的 Java 驻留在 AWS 私有子网中的应用程序通过 AWS Nat 网关连接到 http 服务器。我正在通过 HttpClient
向 HTTP 服务器调用 POST 请求。该请求将需要 10 多分钟才能完成。我已经配置了 1 小时的套接字超时和连接超时,因为这是一个后台任务。但是中间 AWS NAT 网关将在 300 秒 [5 分钟] 后发回 RST 数据包并导致连接重置,我无法增加 NAT 网关超时。所以我需要从我的应用程序端处理这个问题。
我的策略是使用 TCP 保持活动时间,它会每 240 秒发送一个数据包以保持连接处于活动状态。我已经配置了这个 如下
CloseableHttpClient httpClient = HttpClients.createDefault()
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 3600000); //connection Timeout
HttpConnectionParams.setSoTimeout(params, 3600000); // Socket Time out
HttpConnectionParams.setSoKeepalive(params, true); //Enable Socket level keep alive time
然后通过execute方法调用post请求
HttpPost post = new HttpPost("http://url");
HttpResponse response = httpClient.execute(post);
由于我使用的是 Linux 系统,因此我已使用以下 sysctl 值配置服务器:
sysctl -w net.ipv4.tcp_keepalive_time=240
sysctl -w net.ipv4.tcp_keepalive_intvl=240
sysctl -w net.ipv4.tcp_keepalive_probes=10
但是在执行程序时,没有启用保持活动状态,连接失败。
我已经使用 netstat -o 选项检查了这一点,如下所示,keep alive 已关闭
tcp 0 0 192.168.1.141:43770 public_ip:80 ESTABLISHED 18134/java off (0.00/0/0)
有什么方法可以使用 httpclient 从 java 代码设置 TCP keep alive。我还可以看到 HttpConnectionParams
已被弃用。但是我找不到任何新的 class 可以设置 keep alive
我找到了解决问题的方法。奇怪的情况是我无法在 httpclient 中使用一些构建器 class 来传递 socket keep alive 。我在问题中指定的一种方法是使用 HttpConnectionParams,如下所示,但这不起作用,现在不推荐使用此 class。
HttpParams params = httpClient.getParams();
HttpConnectionParams.setSoKeepalive(params, true);
因此,在检查 apache http 文档时,我可以看到现在连接参数通过 RequestConfig class 传递给了 httpclient。此 class 的构建者提供了设置 connection_time_out 和 socket_time_out 的解决方案。但是检查这个源代码我看不到启用 SocketKeepAlive 的选项,这正是我们想要的。因此唯一的解决方案是使用 SocketBuilder class 直接创建 Socket 并将其传递给 HttpClientBuilder。
以下是工作代码
SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setSoTimeout(3600000).build(); //We need to set socket keep alive
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(3600000).build();
CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).
setDefaultSocketConfig(socketConfig).build();
HttpPost post = new HttpPost(url.toString());
HttpResponse response = httpClient.execute(post);
在上面执行时,我可以看到根据我在 linux 内核
中设置的 sysctl 值,在套接字中正确设置了 keep alivetcp 0 0 localip:48314 public_ip:443 ESTABLISHED 14863/java keepalive (234.11/0/0)
如果有人有更好的解决方案来从 Requestconfig class 或任何其他高级构建器 class 启用 Socket Keep alive,我愿意接受建议。
保持 HTTP 连接打开但长时间不活动是一个糟糕的设计选择。 HTTP 是一种请求-响应协议,意味着请求和响应很快。
保持连接打开会保持资源。从服务器(以及网络防火墙和路由器)的角度来看,打开连接并开始请求(在您的情况下为 POST)但长时间不发送任何字节的客户端与客户端无法区分将永远不会 发送任何更多数据,因为它是错误的或恶意的(进行 DOS 攻击)。服务器(和网络硬件)正确地得出结论,正确的做法是关闭连接并回收用于它的资源。您正试图与出于充分理由而发生的正确行为作斗争。即使您设法解决 TCP 关闭问题,您也会发现其他问题,例如 HTTP 服务器超时和数据库超时。
您应该重新考虑两个组件之间的通信设计。也就是说,这看起来像一个 XY 问题。您可能会考虑
- 在 开始 POST 之前,让客户端等到它完成上传以执行 。
- 将上传分成更小、更频繁的上传。
- 使用 HTTP 以外的协议。
上面使用 Socket 的方法在 AWS 网络负载均衡器超时以下重置 tcp_keepalive_intvl 值时效果很好。使用两者,重置允许 java 小时以上连接的 NLB tcp 空闲超时。
有时,如果配置被覆盖,配置不采取 effect.My buildClient 中的 setDefaultSocketConfig 初始修改没有采取 effect.Because 它被 getConnectionManager() 覆盖
public CloseableHttpClient buildClient() throws Exception {
HttpClientBuilder builder = HttpClientBuilder.create()
.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).build()) // did not work
.setConnectionManager(getConnectionManager())
.setRetryHandler(getRequestRetryHandler())
.setConnectionReuseStrategy(getConnectionReuseStrategy())
.setDefaultConnectionConfig(getConnectionConfig())
.setDefaultRequestConfig(getRequestConfig())
.setDefaultHeaders(getDefaultHeaders())
.setDefaultCredentialsProvider(getDefaultCredentialsProvider())
.disableContentCompression() // gzip is not needed. Use lz4 when compress=1
.setDefaultCookieStore(cookieStoreProvider.getCookieStore(properties))
.disableRedirectHandling();
String clientName = properties != null ? properties.getClientName() : null;
if (!Utils.isNullOrEmptyString(clientName)) {
builder.setUserAgent(clientName);
}
return builder.build();
然后我将配置移动到 getConnectionManager(),它工作了。
private PoolingHttpClientConnectionManager getConnectionManager()
throws CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
RegistryBuilder<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory());
if (properties.getSsl()) {
HostnameVerifier verifier = "strict".equals(properties.getSslMode()) ? SSLConnectionSocketFactory.getDefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE;
registry.register("https", new SSLConnectionSocketFactory(getSSLContext(), verifier));
}
//noinspection resource
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(
registry.build(),
null,
null,
new IpVersionPriorityResolver(),
properties.getTimeToLiveMillis(),
TimeUnit.MILLISECONDS
);
connectionManager.setDefaultMaxPerRoute(properties.getDefaultMaxPerRoute());
connectionManager.setMaxTotal(properties.getMaxTotal());
connectionManager.setDefaultConnectionConfig(getConnectionConfig());
connectionManager.setDefaultSocketConfig(SocketConfig.custom().setSoKeepAlive(true).build());
return connectionManager;
}