Страницы

Поиск по вопросам

среда, 18 декабря 2019 г.

Корректное использование Apache HTTP Client в многопоточном приложении

#java #многопоточность #httpclient


Есть Java приложение, основная деятельность которого - автоматизированная работа
в интернете со многих аккаунтов одновременно(НЕ спам или другая плохая деятельность).
Для работы с сетью используется Apache HTTP Client 4.5.2. Приложение ходит в интернет
через разные HTTP proxy(на 1 прокси 2 аккаунта) с авторизацией по логин/пароль, а так
же использует куки.

Изначально, в силу очень плохих знаний Java, среды разработки, и, самое главное,
английского языка, работа многопоточного приложения была построена следующим образом:

Выбираем из CSV файла(примитивный аналог Базы Данных) аккаунт, создаем для него отдельный
поток, в котором создаем объект класса Task, в котором реализована основной функционал
программы(парсинг полученных страниц и принятие решений на основе полученных данных,
а так же обновление CSV файла). В этом объекте(классе) в свою очередь, создается объект
класса Network, в котором осуществляется вся работа с сетью(а полученные данные используются
в Task).

Таким образом, для каждого потока создается свой экземпляр HTTP Client. 
Когда я это делал, я уже примерно знал, что у HttpClient есть особенности работы
в многопоточной среде, но мне казалось что создание отдельного экземпляра для каждого
потока является оптимальным решение этой проблемы.

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

Но когда потоков стало достаточно много, код перестал работать корректно, что вполне
естественно. А именно, при начале работы программы, запускаются все потоки, но достаточно
быстро большинство из них уходит в Ожидание(блокировку), создаваемую HttpClient и не
работает(грубо говоря, зависает).

Естественно, я обратился к Гуглу и документации, и почти сразу понял, что создавать
отдельный экземпляр HTTP Client для каждого потока нельзя, а нужно создавать один на
все приложение, и использовать 

PoolingHttpClientConnectionManager


Но при этом для каждого потока создавать свой HttpContext (в нем, если не путаю,
хранятся куки + в моем случае авторизация на прокси(использую preemptive способ авторизации)).

Подробнее об этом можно прочесть в документации HTTP Client, так же там есть пример,
рекомендую глянуть для того, что бы было понтянее о чем спрашиваю дальше.

В целом все понятно. Но как сделать это конкретно в моем приложении, с учетом его
архитектуры, описанной выше?
Оно специально было так сделано, что бы хоть как то быть правильным с точки зрения
ООП, с задумкой повторного использования отдельных классов в других приложениях.

Конечно, можно сделать все - и выбор аккаунта(и создание потока если он подоходит),
и основную логику работы приложения, и работу с сетью в одном классе, создавая HTTP
Client один раз. Но это выйдет класс огромных размеров, читать и редактировать который
будет очень неудобно, а использовать в других приложениях - практически невозможно.
Мне кажется, что это категорически неправильно с точки зрения ООП, и так делать нельзя.

Как же тогда правильно решить эту задачу? 

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

public GetThread(CloseableHttpClient httpClient, HttpGet httpget)


в каждый поток, а там уже передавать аналогичным способ в класс Network, и работать
с ним. 

Тогда, по идее должна сохраниться структура программы, и при этом не будет проблем
с многопоточностью у HTTP Client. 

Верно ли я думаю? Если нет, то как будет правильно?

P.S. Это мое первое серьезное приложение на Java, пожалуйста, не судите строго, все
приходит с опытом. Заранее искренне благодарю за ответы и посильную помощь. 
    


Ответы

Ответ 1



Собственно главная загвоздка производительности в чем? Насколько я понял, в том, что Вы одновременно создаете много потоков. Одновременное создание большого количества потоков, как вы уже поняли, накладно. В хорошей многопоточной программе есть некий фиксированный пул потоков, который обслуживает систему. Например: public class Task implements Runnable { @Override public void run() { //делаем свои дела } } public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < 10; i++) { Runnable task = new Task(); executor.execute(task); } } В этом примере одновременно будут выполняться не более 4 задач одновременно. Проблема номер два. Допустим вы убрали ручное создание огромного кол-ва потоков и используете пул с 4 потоками. Но так как вы работаете с сетью, вы ждете ответ с сервера. В данном месте поток блокируется, переходит в ожидание: public void run() { Response response = client.execute(...); } Очевидно, что в случае долгого ответа сервера, производительность будет плоха, а процессор не будет нагружен, так как вы просто ждете. Для решения подобных проблем, например, написали Non-blocking I/O. В случае с http клиентами тоже есть не блокирующие библиотеки, например async-http-client: public void run() { AsyncHttpClient client = new AsyncHttpClient(); client.get("https://www.google.com", new AsyncHttpResponseHandler() { @Override public void onSuccess(int statusCode, Header[] headers, byte[] response) { // called when response HTTP status is "200 OK" } }); } После вызова get, текущий поток не блокируется и мы благополучно выходим из метода run, а ответ приходит в onSuccess. Внутри подобные библиотеки устроены таким образом, что в них есть некий пул потоков, обслуживающий выполнение запросов, а в случае ожидания ответа сервера, поток не блокируется, а идет обслуживать другой запрос. Как-то так :) В apache http client тоже есть неблокирующее апи example. Правда думаю, что если делать через nio, то проще какую-нибудь популярную библиотеку заюзать.

Комментариев нет:

Отправить комментарий