Как установить TCP, чтобы сохранить Alive в Java от httpclient

Мое java-приложение, которое находится в частной подсети AWS, подключается к серверу http через шлюз AWS Nat. Я вызываю запрос POST через httpclient к серверу HTTP. Этот запрос займет более 10 минут. Я настроил время ожидания сокета и время соединения 1 час, так как это фоновая задача. Но промежуточный шлюз AWS NAT отправит обратно пакет RST через 300 секунд [5 минут] и приведет к сбросу соединения, поэтому я не могу увеличить время ожидания шлюза 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

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 и, как показано ниже, keep alive выключен

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

Есть ли способ, которым я могу установить TCP, чтобы сохранить в живых из Java-кода, используя httpclient . Также я вижу, что HttpConnectionParams устарели. Но я не смог найти ни одного нового класса, который мог бы поддерживать жизнь

Спасибо

4 ответа

Решение

Я нашел решение проблемы. Любопытно, что я не могу использовать какой-то класс построителя в httpclient для передачи активности сокета. Один метод, как я указал в вопросе, использует HttpConnectionParams, как показано ниже, но это не работает, и этот класс устарел.

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

Поэтому, проверяя документы Apache http, я вижу, что теперь параметры соединения передаются httpclient через класс RequestConfig. Разработчики этого класса предоставляют решение для установки connection_time_out и socket_time_out. Но, проверяя этот код, я не смог увидеть опцию включения SocketKeepAlive, чего мы и хотим. Таким образом, единственным решением является непосредственное создание Socket с использованием класса SocketBuilder и передача его в 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);

Выполняя выше, я вижу, что keep live правильно установлен в сокете на основе значений sysctl, которые я установил в ядре Linux.

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

Если у кого-то есть лучшее решение для включения поддержки Socket Keep из класса Requestconfig или любого другого класса построителя высокого уровня, я открыт для предложений.

Держать HTTP-соединение открытым, но неактивным в течение длительного времени - плохой выбор. HTTP - это протокол запроса-ответа, подразумевающий, что запросы и ответы быстрые.

Удержание открытого соединения удерживает ресурсы. С точки зрения сервера (и сетевых брандмауэров и маршрутизаторов) клиент, который открывает соединение и начинает запрос (POST в вашем случае), но не отправляет байты в течение длительного периода, неотличим от клиента, который никогда не будет отправлять какие-либо сообщения. больше данных, потому что они неисправны или злонамеренны (проводят DOS-атаку). Сервер (и сетевое оборудование) вправе сделать вывод, что правильное решение - отключить соединение и освободить ресурсы, используемые для него. Вы пытаетесь бороться с правильным поведением, которое происходит по уважительным причинам. Даже если вам удастся обойти завершение работы TCP, вы обнаружите другие проблемы, такие как таймауты HTTP-сервера и базы данных.

Вместо этого вам следует пересмотреть конструкцию связи между двумя компонентами. То есть это выглядит как проблема XY. Вы могли бы рассмотреть

  • Перед тем, как запустить POST, клиент должен дождаться завершения загрузки.
  • Разделение загрузок на более мелкие и более частые загрузки.
  • Используйте протокол, отличный от HTTP.

Описанный выше подход с Socket прекрасно работал со сбросом значения tcp_keepalive_intvl ниже тайм-аута AWS Network Load Balancer. Используя оба, сбросьте таймаут простоя tcp NLB, который позволил java hour+ соединения.

Иногда, если конфигурация перезаписывается, конфигурация не вступает в силу. Моя первоначальная модификация setDefaultSocketConfig в buildClient не вступила в силу. Поскольку она перезаписывается 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;
}
Другие вопросы по тегам