【问题标题】:How to set TCP keep Alive from HttpClient?如何从 HttpClient 设置 TCP 保持活动状态?
【发布时间】:2018-11-19 13:00:21
【问题描述】:

位于 AWS 私有子网中的 Java 应用程序通过 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 选项检查了这一点,如下所示保持活动已关闭

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

有什么方法可以让我使用 httpclient 从 java 代码中设置 TCP 保持活动状态。我还可以看到 HttpConnectionParams 已被弃用。但是我找不到任何可以设置keep alive的新类

【问题讨论】:

  • 是的,我已经仔细检查过了。 http 服务器的超时时间也是 1 小时。这是导致问题的NAT网关。我已经通过 curl 尝试了相同的 post call,并且请求成功的超时时间小于 300 秒。如果我给出的超时时间大于 300,它就会失败。此外,当我们不使用 NAT 网关时,请求也会成功。

标签: java apache-httpclient-4.x tcp-keepalive


【解决方案1】:

我找到了解决问题的方法。奇怪的情况是我无法在 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,我愿意接受建议。

【讨论】:

    【解决方案2】:

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

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

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

    • 让客户端等待直到它完成上传以执行开始 POST。
    • 将上传分成更小、更频繁的上传。
    • 使用 HTTP 以外的协议。

    【讨论】:

    • 是的,我同意你的观点,即 HTTP 请求和响应必须快速完成。但在我们的案例中,有些任务需要时间才能完成。我们正在通过 rabbitmq 将这些任务转移到基于侦听器的模型中。但现在我需要坚持这种方法。
    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

      有时候,如果配置被覆盖,配置不生效。我在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;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-03-09
        • 1970-01-01
        • 2010-11-19
        • 1970-01-01
        • 1970-01-01
        • 2019-09-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多