5

我位于 AWS 私有子网中的 Java 应用程序通过 AWS Nat 网关连接到 http 服务器。我正在通过HttpClientHTTP 服务器调用 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 选项检查了这个,如下所示保持活动关闭

tcp        0      0 192.168.1.141:43770     public_ip:80          ESTABLISHED 18134/java           off (0.00/0/0)

有什么方法可以使用 httpclient 从 java 代码中设置 TCP 保持活动状态。我也可以看到HttpConnectionParams已弃用。但是我找不到任何可以设置保持活力的新课程

4

4 回答 4

4

我找到了解决问题的方法。奇怪的情况是我无法在 httpclient 中使用一些构建器类来传递套接字保持活动。我在问题中指定的一种方法是使用 HttpConnectionParams 如下,但这不起作用,现在不推荐使用此类。

HttpParams params = httpClient.getParams();
HttpConnectionParams.setSoKeepalive(params, true);

因此,在检查 apache http 文档时,我可以看到现在连接参数通过 RequestConfig 类传递给 httpclient。此类的构建器提供了设置 connection_time_out 和 socket_time_out 的解决方案。但是检查这个源代码我看不到启用 SocketKeepAlive 的选项,这正是我们想要的。所以唯一的解决方案是直接使用 SocketBuilder 类创建一个 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 值在套接字中正确设置了保持活动状态

tcp        0      0 localip:48314     public_ip:443     ESTABLISHED 14863/java          keepalive (234.11/0/0)

如果有人有更好的解决方案来从 Requestconfig 类或任何其他高级构建器类中启用 Socket Keep alive,我愿意接受建议。

于 2018-11-20T07:44:29.150 回答
1

保持 HTTP 连接打开但长时间不活动是一个糟糕的设计选择。HTTP 是一种请求-响应协议,意味着请求和响应都很快。

保持连接打开保持资源。从服务器(以及网络防火墙和路由器)的角度来看,打开连接并开始请求(在您的情况下为 POST)但长时间不发送任何字节的客户端与永远不会发送任何字节的客户端无法区分更多数据,因为它是错误的或恶意的(进行 DOS 攻击)。服务器(和网络硬件)认为正确的做法是关闭连接并回收用于连接的资源是正确的。您正试图与出于正当理由发生的正确行为作斗争。即使您设法解决 TCP 关闭问题,您也会发现其他问题,例如 HTTP 服务器超时和数据库超时。

相反,您应该重新考虑两个组件之间的通信设计。也就是说,这看起来像一个 XY 问题。你可能会考虑

  • 在开始 POST之前,让客户端等待它完成上传。
  • 将上传分成更小、更频繁的上传。
  • 使用 HTTP 以外的协议。
于 2018-11-20T09:43:03.277 回答
0

上面使用 Socket 的方法在 AWS 网络负载均衡器超时以下重置 tcp_keepalive_intvl 值时效果很好。使用两者,重置允许 java 小时+连接的 NLB tcp 空闲超时。

于 2019-03-01T18:57:21.773 回答
0

有时候,如果配置被覆盖,配置不生效。我在buildClient中初始修改setDefaultSocketConfig没有生效。因为被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;
}
于 2021-10-29T06:51:07.777 回答