繁体   English   中英

如何从 HttpClient 设置 TCP 保持活动状态?

[英]How to set TCP keep Alive from HttpClient?

我位于 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已被弃用。 但是我找不到任何可以设置保持活动状态的新课程

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

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

因此,在检查apache http docs时,我可以看到现在连接参数已通过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,我欢迎您提出建议。

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

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

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

  • 在启动POST 之前,让客户端等待直到要执行的完整上载。
  • 将上传内容分成更小,更频繁的上传内容。
  • 使用除HTTP以外的协议。

Socket上的上述方法在AWS Network Load Balancer超时以下重置tcp_keepalive_intvl值时可以很好地工作。 同时使用两者,重置允许java hour +连接的NLB tcp空闲超时。

有时候,如果配置被覆盖,配置不会生效。

    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;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM