簡體   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