Как установить 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;
}