简体   繁体   English

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

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

My Java application which resides in AWS private subnet connects to an http server via AWS Nat gateway.我位于 AWS 私有子网中的 Java 应用程序通过 AWS Nat 网关连接到 http 服务器。 I am calling a POST request via HttpClient to the HTTP server.我正在通过HttpClient向 HTTP 服务器调用 POST 请求。 That request will take more than 10 minutes to complete.该请求将需要 10 多分钟才能完成。 I have configured a socket time out and connection timeout of 1 hour as this this a background task .我已经配置了一个套接字超时和 1 小时的连接超时,因为这是一个后台任务。 But the intermediate AWS NAT gateway will send back a RST packet after 300 secs [5 mins] and cause the connection to get resetted , there is no way i can increase the NAT gateway timeout.但是中间 AWS NAT 网关将在 300 秒 [5 分钟] 后发回一个 RST 数据包并导致连接重置,我无法增加 NAT 网关超时。 So i need to handle the problem from my application side.所以我需要从我的应用程序方面处理这个问题。

My strategy is to use a TCP keep alive time which will send a packet say every 240 secs to keep the connection active.我的策略是使用 TCP 保持活动时间,它将每 240 秒发送一个数据包以保持连接处于活动状态。 I have configured this as below我已将其配置如下

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

and then call the post request via execute method然后通过execute方法调用post请求

HttpPost post = new HttpPost("http://url");
HttpResponse response = httpClient.execute(post);

Since I am using a Linux system I have configured the server with following sysctl values:由于我使用的是 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

But while executing the program the keep alive is not enabled and connection fails as previous.但是在执行程序时,未启用保持活动并且连接像以前一样失败。

I have checked this with netstat -o option and as shown below keep alive is off我已经用 netstat -o 选项检查过这个,如下所示保持活动关闭

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

Is there any way i can set TCP keep alive from java code using httpclient .有什么方法可以使用 httpclient 从 java 代码设置 TCP 保持活动状态。 Also I can see HttpConnectionParams are deprecated.我也可以看到HttpConnectionParams已被弃用。 But I couldn't find any new class which can set keep alive但是我找不到任何可以设置保持活动状态的新课程

I have found a solution to the problem . 我已经找到解决问题的办法。 Curious case is there is no way i can use some builder class in httpclient to pass socket keep alive . 奇怪的情况是,我无法使用httpclient中的某些构建器类来传递套接字保持活动状态。 One method as i specified in the question is using HttpConnectionParams as below but this is not working and this class is now deprecated. 我在问题中指定的一种方法是使用HttpConnectionParams,如下所示,但这不起作用,并且现在不推荐使用此类。

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

So while checking apache http docs I can see that now connection parameters are passed to httpclient via RequestConfig class . 因此,在检查apache http docs时,我可以看到现在连接参数已通过RequestConfig类传递给httpclient。 Builders of this class provide solution to set connection_time_out and socket_time_out. 此类的构建器提供了用于设置connection_time_out和socket_time_out的解决方案。 But checking the socurce code of this I couldnt see an option to enable SocketKeepAlive which is what we want. 但是检查此代码,我看不到启用SocketKeepAlive的选项。 So the only solution is directly creating a Socket using SocketBuilder class and pass that to the HttpClientBuilder. 因此,唯一的解决方案是使用SocketBuilder类直接创建一个Socket,并将其传递给HttpClientBuilder。

Following is the working code 以下是工作代码

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

While executing above i can see that keep alive is properly set in the socket based on the sysctl values i set in linux kernel 在上面执行时,我可以看到我根据在Linux内核中设置的sysctl值在套接字中正确设置了保持活动状态

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

If some one has a better solution to enable Socket Keep alive from Requestconfig class or any other high level builder class i am open to suggestions. 如果有人有更好的解决方案来启用Requestconfig类或任何其他高级构建器类的Socket Keep alive,我欢迎您提出建议。

Keeping an HTTP connection open but inactive for a long period is a bad design choice. 保持HTTP连接打开但长时间处于非活动状态是一个糟糕的设计选择。 HTTP is a request-response protocol, with the implication that requests and responses are quick. HTTP是一种请求-响应协议,意味着请求和响应很快。

Holding a connection open holds resources. 保持连接打开将保留资源。 From the perspective of the server (and network firewalls and routers) a client that opens a connection and begins a request (A POST in your case) but does not send any bytes for a long period is indistinguishable from a client that will never send any more data, because it is faulty or malicious (conducting a DOS attack). 从服务器(以及网络防火墙和路由器)的角度来看,打开连接并开始请求(在您的情况下为POST)但长时间不发送任何字节的客户端与永远不会发送任何字节的客户端没有区别。更多数据,因为它有故障或恶意(进行DOS攻击)。 The server (and network hardware) is right to conclude that the right thing to do is to shutdown the connection and reclaim the resources used for it. 服务器(和网络硬件)得出的正确结论是,正确的做法是关闭连接并回收用于其的资源,这是正确的。 You are trying to fight against correct behaviour that occurs for good reasons. 您正试图与出于正确原因而发生的正确行为作斗争。 Even if you manage to workaround the TCP shutdown you will find other problems, such as HTTP server timeouts and database timeouts. 即使您设法解决TCP关闭问题,您也会发现其他问题,例如HTTP服务器超时和数据库超时。

You should instead be reconsidered the design of communication between the two components. 相反,您应该重新考虑两个组件之间的通信设计。 That is, this looks like an XY Problem. 也就是说,这看起来像XY问题。 You might consider 您可能会考虑

  • Having the client wait until it has a complete upload to perform before starting the POST. 在启动POST 之前,让客户端等待直到要执行的完整上载。
  • Splitting the uploads into smaller, more frequent uploads. 将上传内容分成更小,更频繁的上传内容。
  • Use a protocol other than HTTP. 使用除HTTP以外的协议。

The approach above with Socket worked beautifully with a reset of tcp_keepalive_intvl value below the AWS Network Load Balancer timeout. Socket上的上述方法在AWS Network Load Balancer超时以下重置tcp_keepalive_intvl值时可以很好地工作。 Using both, reset the NLB tcp idle timeout that allowed java hour+ connections. 同时使用两者,重置允许java hour +连接的NLB tcp空闲超时。

Sometimes, if the configuration is overwritten, the configuration does not take effect.My initial modification of setDefaultSocketConfig in buildClient didn't take effect.Because it is overwritten by 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();

And then I move the config to getConnectionManager(),and it work.然后我将配置移动到 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