Отправка асинхронных HTTP-запросов в Java с помощью Spring Boot - PullRequest
0 голосов
/ 21 февраля 2019

Я работаю над приложением, которое должно непрерывно тестировать 1000 прокси-серверов.Приложение основано на Spring Boot.

В настоящее время я использую метод декорирования @ Async , который использует прокси-сервер и возвращает результат.

Я часто получаю ошибку OutOfMemory, и обработка очень медленная.Я предполагаю, что это потому, что каждый асинхронный метод выполняется в отдельном потоке, который блокирует ввод-вывод?

Везде, где я читаю об асинхронности в Java, люди смешивают параллельное выполнение в потоках с неблокирующим вводом-выводом.В мире Python существует асинхронная библиотека, которая выполняет запросы ввода-вывода в одном потоке.Пока метод ожидает ответа от сервера, он начинает выполнять другой метод.

Я думаю, что в моем случае мне нужно что-то подобное, потому что Spring AsAsc не подходит для меня.Может ли кто-нибудь помочь мне устранить путаницу и предложить мне, как мне решить эту проблему?

Я хочу одновременно проверять сотни прокси-серверов без чрезмерной нагрузки.Я читал об HTTP-клиенте Apache Async, но не знаю, подходит ли он?

Это конфигурация пула потоков, которую я использую:

    public Executor proxyTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2 - 1);
        executor.setMaxPoolSize(100);
        executor.setDaemon(true);
        return executor;
    }

1 Ответ

0 голосов
/ 21 февраля 2019

Я часто получаю ошибку OutOfMemory, и обработка идет очень медленно.Я предполагаю, что это потому, что каждый асинхронный метод выполняется в отдельном потоке, который блокирует ввод-вывод?

Для OOME я объясняю это во втором пункте.
О медлительности, этодействительно относится к операциям ввода-вывода, выполняемым при обработке запросов / ответов.
Проблема заключается в количестве параллельно работающих потоков.
При вашей фактической конфигурации число пулов максимально не достигается (я объясняюпочему ниже).Предположим, что corePoolSize==10 в вашем случае.Это означает, что 10 потоков работают параллельно.Предположим, что каждый поток выполняется около 3 секунд для тестирования сайта.
Это означает, что вы тестируете сайт примерно за 0,3 секунды.Для тестирования 1000 сайтов это занимает 300 секунд.
Это достаточно медленно и важной частью времени является время ожидания: ввод / вывод для отправки / получения запроса / ответа с сайта, который в настоящее время тестируется.
Для увеличения общегоСкорость, вы, вероятно, должны изначально работать параллельно гораздо больше потоков, чем ваша основная емкость.Таким образом, время ожидания ввода / вывода будет меньше проблем, поскольку планирование между потоками будет частым, и поэтому вы будете иметь некоторые обработки ввода / вывода без значения для потоков, пока они приостановлены.


Он должен справиться с проблемой OOME и, вероятно, значительно улучшить время выполнения, но не гарантирует, что вы получите очень короткое время.
Для достижения этого вам, вероятно, следует использовать многопоточную логикуболее тонко и полагаться на API / библиотеки с неблокирующим IO.

Некоторая информация о официальной документации , которая должна быть полезной.
Эта часть объясняет общую логику при отправке задачи (выделено мной):

Конфигурация пула потоков также должна рассматриваться с учетом емкости очереди исполнителя.Полное описание взаимосвязи между размером пула и емкостью очереди см. В документации по ThreadPoolExecutor. Основная идея заключается в том, что при отправке задачи исполнитель сначала пытается использовать свободную нить, если число активных нитей в настоящее время меньше размера ядра.Если размер ядра достигнут, задача добавляется в очередь, если ее емкость еще не достигнута.Только тогда, если емкость очереди достигнута, исполнитель создает новый поток сверх размера ядра.Если максимальный размер также был достигнут, то исполнитель отклоняет задачу.

И это объясняет последствия для размера очереди (акцент все еще мой):

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

Короче говоря: вы не установили размер очереди, который по умолчанию не ограничен (Integer.MAX_VALUE).Таким образом, вы заполняете очередь несколькими сотнями задач, которые появятся только намного позже.Эти задачи занимают много памяти, тогда как OOME повышается.

Кроме того, как объяснено в документации, этот параметр беспомощен в случае неограниченной очереди, поскольку только при заполнении очереди будет создан новый поток:

executor.setMaxPoolSize(100);

Установка обеих данных с соответствующимизначения имеют больше смысла:

public Executor proxyTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2 - 1);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100); 
    executor.setDaemon(true);
    return executor;
}

Или в качестве альтернативы используйте пул фиксированного размера с тем же значением для начального и максимального размера пула:

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

public Executor proxyTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(100);
    executor.setMaxPoolSize(100);
    executor.setDaemon(true);
    return executor;
}

Обратите внимание также, что 1000-кратный вызов асинхронной службы без паузывреден с точки зрения памяти, так как он не может обращаться с ними прямо.Возможно, вам следует разделить эти вызовы на более мелкие части (2, 3 или более), выполнив между ними thread.sleep ().

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