Apache HttpClient и HttpConnection в многопоточном приложении

В моем многопоточном приложении я отправляю несколько http-запросов на несколько http-серверов, я бы сказал, 10 серверов, 300 разных запросов на сервер, примерно раз в час, ничего серьезного.

Мой вопрос: должен ли я хранить один HttpClient для всех исходящих соединений? Может быть, по одному на уникальный целевой сервер? или по одному на «итерацию» (это занимает около 10 минут в начале каждого часа)?

В настоящее время я использую один PoolingHttpClientConnectionManager и HttpClientBuilder.setConnectionManager(connectionManager).build() для каждого запроса.

У меня есть ощущение реальной траты ресурсов, и я также вижу много соединений в состоянии ESTABLISHED на сервер, хотя я использую диспетчер соединений пула. (Запросы для каждого сервера отправляются один за другим и не являются одновременными)


person Nati    schedule 11.03.2016    source источник


Ответы (4)


В настоящее время я использую один PoolingHttpClientConnectionManager и HttpClientBuilder.setConnectionManager(connectionManager).build() для каждого запроса.

Создание нового HttpClient для каждого запроса — огромная трата времени. Вы должны использовать HttpClient для каждой конфигурации (каждый клиент может иметь свой диспетчер соединений, максимальное количество одновременных запросов и т. д.) или для каждого независимого модуля вашего приложения (чтобы не создавать зависимости между независимыми модулями).

Также не забывайте, что .build() возвращает CloseableHttpClient, что означает, что вы должны вызывать httpClient.close(), когда закончите его использовать, иначе вы можете утечь ресурсы.


Обновление в ответ на комментарий от @Nati:

что будет "впустую"? Является ли HttpClient тяжелым объектом?

http://grepcode.com/file/repo1.maven.org/maven2/org.apache.httpcomponents/httpclient/4.3.1/org/apache/http/impl/client/HttpClientBuilder.java?av=f#693 Как вы можете видеть, это много кода, и его бессмысленно выполнять при каждом запросе. Это ненужное потребляет процессор и создает много мусора, который снижает производительность всего приложения. Чем меньше выделений вы сделаете - тем лучше! Другими словами, никаких преимуществ от создания нового клиента для каждого запроса — только недостатки.

имеет ли смысл хранить его как bean-компонент на протяжении всего срока службы приложения?

ИМХО да, если только не используется очень (очень) редко.

отношение между HttpConnection и HttpClient

Каждый http-клиент может выполнять несколько http-запросов. Каждый запрос выполняется в контексте клиента (его конфигурация - т.е. прокси, параллелизм, поддержка активности и т.д.) Каждый ответ на запрос должен быть закрыт (reset(), close(), точное название не помню ), чтобы освободить соединение, чтобы его можно было повторно использовать для другого запроса.

person Svetlin Zarev    schedule 14.03.2016
comment
Спасибо. не могли бы вы уточнить связь между HttpConnection и HttpClient? что будет впустую? Является ли HttpClient тяжелым объектом? имеет ли смысл хранить его как bean-компонент на протяжении всего жизненного цикла приложения (таким образом избегая необходимости close() его? - person Nati; 14.03.2016

Я бы сказал, если он не сломан, не чините его. Что я имею в виду, если простейшая возможная конфигурация соответствует вашим потребностям, используйте ее и не добавляйте никаких сложностей только для того, чтобы позаботиться о будущих потребностях в масштабируемости. Дополнительные части означают дополнительную сложность и больше ошибок. Как только вы увидите, что текущая конфигурация больше не выдерживает повышенной нагрузки, сделайте оценку и добавьте ресурсы. надеюсь, это поможет

person Michael Gantman    schedule 14.03.2016
comment
Да, premature optimization is the root of all evil. сказал Кнут. Но это не оптимизация. Мне нужна передовая практика и более глубокое понимание важного фреймворка, который я использую. - person Nati; 14.03.2016

Я согласен с @Michael Gantman в том, что не исправлю это.

Я бы сказал, что исправление или неисправление зависит от вашего профиля нагрузки.

Сохранять или не поддерживать связи?

Например, если вы отправляете 300 запросов сразу на 10 серверов, а после этого в течение часа ничего не делаете, то с точки зрения ресурсов нет смысла держать какие-либо TCP/IP-соединения. открывается (из-за использования HTTP/1.1) на целый час.

Однако, если вы разговариваете с сервером каждые 5 секунд, вы можете оставить соединение открытым. Кроме того, если вы хотите свести к минимуму задержки, устраняя многократное установление соединения, вы можете рассмотреть возможность сохранения соединений открытыми.

Для этого вы должны использовать HTTP/1.1. Вы можете найти множество примеров, например. DefaultHttpClient поддерживает соединение при нескольких запросах

Сколько связей сохранить?

Опять же, зависит от вашего профиля нагрузки. Вы сказали, что у вас есть 10 серверов. Если вы отправляете данные для одного сервера последовательно, то одного http-соединения на сервер с http/1.1 вполне достаточно. Однако, если вы хотите сделать что-то более быстрое (например, загрузить два изображения параллельно), вы можете открыть несколько соединений с одним и тем же сервером. (Конечно, это означает, что ваше приложение действительно многопоточное.)

Заключение

Если это не критичное по времени приложение, проще всего ничего не объединять, а просто обращаться к серверам, когда у вас есть данные для отправки. Вы можете начать чрезмерно оптимизировать это и бороться за 10 мс улучшения ценой серьезной случайной сложности.

person Gee Bee    schedule 14.03.2016
comment
На самом деле я позволяю менеджеру poolinghttpclientconnectionрешать, сохранять ли соединение открытым или нет. Меня интересует в основном отношение к httpclient - person Nati; 14.03.2016
comment
Мне интересно, как он может выяснить, когда стоит поддерживать соединение или прерывать соединение. Он не знает вашего профиля нагрузки, поэтому может случиться, что какое-то соединение будет разорвано, даже если оно необходимо через 2 секунды, или не будет разорвано соединение, даже если в течение следующих получаса не будет связи. Я предполагаю, что вы либо полностью игнорируете это, как мы предлагаем, либо иным образом должны копаться в деталях, если хотите полностью оптимизировать это, не так ли? - person Gee Bee; 14.03.2016
comment
Есть лучшие практики. Это слишком старо, но именно такой ответ я ищу. stackoverflow.com/q/1281219/1517569 - person Nati; 14.03.2016

В заголовке HTTP 1.0 вашего клиентского запроса вам нужно

Connection: keep-alive

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

HTTP 1.1 предоставляет эту функциональность по умолчанию, но время ожидания по умолчанию довольно короткое. Возможно, там есть какая-то конфигурация. В любом случае, если вы получите ответ с Connection: close в заголовке, вы должны закрыть соединение.

Дополнительные сведения см. в разделе rfc2616, особенно в разделе 8 "Постоянные подключения".

Таким образом, казалось бы, что правильно сделать, это обеспечить обработку HTTP 1.1 (соединения по умолчанию остаются открытыми с 1.1) и не делать ничего «особенного» с HttpClient. Согласно второму и третьему разделам главной страницы HttpClient, по умолчанию клиент будет удерживать постоянное соединение как можно дольше.

Мои рекомендации заключаются в том, чтобы (при добавлении подпрограммы/потока/контроллера на стороне клиентского соединения) все связанные подключения к определенному серверу/порту упорядочивались в одной и той же области (все в одном потоке или упорядочены в том же порядке), это вероятно, уменьшит вероятность того, что вы столкнетесь с логикой закрытия соединения; но вы не можете заставить соединение оставаться открытым (по очевидным причинам).

person Edwin Buck    schedule 14.03.2016
comment
Спасибо. Но если я вижу в netstat соединение в состоянии ESTABLISHED, значит, скорее всего, http-сессия еще жива, нет? - person Nati; 14.03.2016
comment
Кстати, ваши ссылки относятся к HttpClient 3.1, что довольно сильно отличается от версии 4.5, которую я использую. - person Nati; 14.03.2016
comment
Не совсем. Вы видите мнение вашего клиента о статусе соединения. Часто это правильно, но может быть и неправильно. По сути, единственный способ действительно узнать, действительно ли установленное соединение можно использовать, — это попытаться его использовать. Это не удастся, если оно непригодно для использования, и даже если это удастся, это снова будет последним известным статусом соединения, а не контрактом. Начальные соединения могут (в зависимости от деталей) иногда быть хуже, оптимизируя соединение без связи с сервером до тех пор, пока первый байт не будет фактически передан. - person Edwin Buck; 14.03.2016
comment
@Nati Ссылки на мои документы могут быть старыми, но большая часть этого материала находится в стандартах протокола, которые даже старше, чем документы, на которые я ссылался (ну, за исключением rfc). Соединения нельзя считать действительно открытыми, когда вы видите, что они установлены. Вы должны передать ему данные (и прочитать ответ), чтобы было известно, что он работает. В конце концов, тайм-ауты приведут все в соответствие с реальностью, но данные некоторое время выглядят хорошо, прежде чем вы обнаружите, что их нельзя использовать. - person Edwin Buck; 14.03.2016