Производительность: Apache HttpAsyncClient против многопоточного URLConnection - PullRequest
0 голосов
/ 07 декабря 2018

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

  1. Использование Apache HttpAsyncClient и CompletableFutures:

    try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
    .setMaxConnPerRoute(2000).setMaxConnTotal(2000)
    .setUserAgent("Mozilla/4.0")
    .build()) {
    httpclient.start();
    HttpGet request = new HttpGet("http://bing.com/");
    long start = System.currentTimeMillis();
    CompletableFuture.allOf(
            Stream.generate(()->request).limit(1000).map(req -> {
                CompletableFuture<Void> future = new CompletableFuture<>();
                httpclient.execute(req, new FutureCallback<HttpResponse>() {
                    @Override
                    public void completed(final HttpResponse response) {
                        System.out.println("Completed with: " + response.getStatusLine().getStatusCode())
                        future.complete(null);
                    }
                    ...
                });
                System.out.println("Started request");
                return future;
    }).toArray(CompletableFuture[]::new)).get();
    
  2. Обычный подход потока к запросу:

    long start1 = System.currentTimeMillis();
    URL url = new URL("http://bing.com/");
    ExecutorService executor = Executors.newCachedThreadPool();
    
    Stream.generate(()->url).limit(1000).forEach(requestUrl ->{
        executor.submit(()->{
            try {
                URLConnection conn = requestUrl.openConnection();
                System.out.println("Completed with: " + conn.getResponseCode());
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        System.out.println("Started request");
    });
    

В нескольких прогонах я заметил, что традиционный подход заканчивался почти вдвое быстрее, чем асинхронный / будущий подход .

Хотя я ожидал, что выделенные потоки будут работать быстрее, будет ли эта разница заметной или, возможно, что-то не так с асинхронной реализацией?Если нет, то каков правильный подход, чтобы пойти сюда?

1 Ответ

0 голосов
/ 10 декабря 2018

Вопрос на месте зависит от лота факторов:

  • аппаратного обеспечения
  • операционной системы (и ее конфигурации)
  • Внедрение JVM
  • Сетевые устройства
  • Поведение сервера

Первый вопрос - должна ли эта разница быть такой замечательной?

Зависит от нагрузки, размера пула и сети, но может быть намного больше, чем наблюдаемый коэффициент 2 в каждом из направлений (в пользу асинхронного или многопоточного решения).Согласно вашему последующему комментарию, разница больше из-за неправильного поведения, но ради аргумента я объясню возможные случаи.

Выделенные потоки могут быть довольно обременительными.(Обработка прерываний и планирование потоков выполняется операционной системой в случае, если вы используете JVM Oracle [HotSpot], поскольку эти задачи делегированы.) Операционная система / система может перестать отвечать, если слишком много потоков и, следовательно, замедляет пакетную обработку (или другие задачи).Существует множество административных задач, связанных с управлением потоками, поэтому пул потоков (и соединений) - это вещь.Хотя хорошая операционная система должна обрабатывать несколько тысяч одновременных потоков, всегда есть вероятность того, что произойдут какие-то ограничения или (ядро) событие.

Здесь пул и асинхронное поведение пригодятся.Например, есть пул из 10 физических потоков, выполняющих всю работу.Если что-то заблокировано (в этом случае ожидает ответа сервера), оно переходит в состояние «Заблокировано» (см. Изображение), и следующая задача заставляет физический поток выполнить некоторую работу.Когда поток уведомляется (данные поступают), он становится «Runnable» (с этого момента механизм пула может его забрать [это может быть решение, реализованное в ОС или JVM]).Для дальнейшего чтения состояний потоков я рекомендую W3Rescue .Чтобы лучше понять пул потоков, я рекомендую эту статью baeldung .

Thread transitions

Второй вопрос - что-то не так сасинхронная реализация?Если нет, то каков правильный подход?

Реализация в порядке, с этим проблем нет.Поведение просто отличается от многопоточного.Основным вопросом в этих случаях является то, что SLA-ы (соглашения об уровне обслуживания).Если вы являетесь единственным «клиентом» службы, то в основном вам нужно выбрать между задержкой или пропускной способностью , но решение повлияет только на вас. В основном это не так, поэтому я бы порекомендовал какой-топул, который поддерживается библиотекой, которую вы используете.

Третий вопрос - Однако я только что отметил, что время, затрачиваемое на чтение потока ответов в виде строки, примерно одинаково. Интересно, почемуis?

Скорее всего, сообщение получено полностью в обоих случаях (возможно, ответ - это не поток, а всего лишь несколько http-пакетов), но если вы читаете только заголовок, который не требует ответаСам должен быть проанализирован и загружен в регистры ЦП, таким образом уменьшая задержку чтения фактических полученных данных. Я думаю, что это отличная репрезентация в задержках ( source и source ): Reach times

Этот ответ получился довольно длинным, поэтому TL.DR.: масштабирование - это по-настоящемуУ хардкорной темы, это зависит от многих вещей:

  • аппаратное обеспечение: количество физических ядер, емкость многопоточности, скорость памяти, сетевой интерфейс
  • операционная система (и ее конфигурация): управление потоками, обработка прерываний
  • Реализация JVM: управление потоками (внутренними или внешними по отношению к ОС), не говоря уже о GC и JITконфигурации
  • Сетевые устройства: некоторые ограничивают одновременные соединения с данного IP, некоторые пулы не HTTPS подключений и действуют как прокси
  • Поведение сервера: работники в пулах или по запросу и т. Д.

Скорее всего, в вашем случае узким местом был сервер, так как оба метода дали исправленный случай в исправленном случае.(HttpResponse::getStatusLine().getStatusCode() and HttpURLConnection::getResponseCode()).Чтобы дать правильный ответ, вы должны измерить производительность ваших серверов с помощью таких инструментов, как JMeter или LoadRunner и т. Д., А затем соответствующим образом изменить размер вашего решения. Эта статья больше о пуле соединений с БД, но логика применима и здесь.

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