#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, то проще какую-нибудь популярную библиотеку заюзать.
Комментариев нет:
Отправить комментарий