Повышение производительности массовых HTTP-вызовов REST параллельно вызову методов GET - PullRequest
0 голосов
/ 21 января 2019

В разрабатываемом приложении мне нужно выполнить огромное количество REST-вызовов.Архитектура ресурсов REST API, с которыми мне нужно взаимодействовать, является иерархической и выглядит следующим образом:

/api/continents - return list of all Earth's continents
/api/continents/{continent_name}/countries - return list of all countries on mentioned continent
/api/continents/{continent_name}/countries/{country_name}/cities - return list of all cities in mentioned country

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

Сначала я попытался реализовать свой метод получения всех городов из этого APIбез распараллеливания только при последовательных вызовах.Примерно так:

private List<City> getCities() {
    List<Continent> continents = getAllContinents(); //HTTP GET call
    List<Country> countries = new ArrayList<>();
    for (Continent continent: continents) {
        countries.addAll(getAllCountriesOfContinent(continent));
    }
    List<City> cities = new ArrayList<>();
    for (Country country : countries) {
        cities.addAll(getAllCitiesOfCountry(country));
    }
    return cities;
}

Но такой подход работал слишком медленно (в конкретных числах он выполнялся около 7 часов).Я решил попробовать улучшить его, используя Java Parallel Streams и CompletableFuture, и получил такие методы:

private List<City> getCities() {
    return getAllContinents()
        .parallelStream()
        .map(continent -> getAllCountriesOfContinent(continent))
        .flatMap(feature -> feature.join().parallelStream())
        .map(country -> getAllCitiesOfCountry(country))
        .flatMap(feature -> feature.join().parallelStream())
        .collect(Collectors.toList());
}

Где методы getAllCountriesOfContinent и getAllCitiesOfCountry возвращают списки CompletableFuture и выглядят так:

private CompletableFuture<List<Country>> getAllCountriesOfContinent(Continent continent) {
    return CompletableFuture.supplyAsync(() -> {
        return restClient.getDataFromApi(continent);
    });
}

private CompletableFuture<List<City>> getAllCitiesOfCountry(Country country) {
    return CompletableFuture.supplyAsync(() -> {
        return restClient.getDataFromApi(country);
    });
}

СПри таком рефакторинге у меня получился хороший прирост производительности (он занимал около 25-30 минут).Но я думаю, что я мог бы улучшить его, используя Java ThreadPoolExecutors и Threads или инфраструктуру ForkJoin.Помогут ли такие подходы повысить производительность моего кода или для этого есть какие-то другие специальные методы / алгоритмы / инфраструктуры?

Ответы [ 2 ]

0 голосов
/ 22 января 2019

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

Тем более, что в Java нет сопрограмм, функция ParallelsStream может быть хорошим и разумным выбором для одновременного управления несколькими HTTP-запросами в полете, но на самом деле это не самая важная часть решения, на котором вы должны сосредоточиться.

На чем вам следует сосредоточиться, так это на деталях сети, а не на процессоре. Такая ситуация, в частности, напоминает мне HTTP / 2, который должен позволять запускать несколько таких запросов одновременно. Вам также следует изучить HTTP Pipelining, который поддерживается в более ранних версиях, но гораздо сложнее в настройке.

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

Помогут ли такие подходы повысить производительность?

Ответ: вероятно.

Видите ли, parallelStream() дает вам реализацию по умолчаниюмногопоточность (и под прикрытием эта операция фактически использует инфраструктуру ForkJoin).

Другими словами: вы всегда можете сделать шаг назад и потратить много часов на эксперименты, в которых вы используете разные низкоуровневые подходы и измеряете соответствующие результаты.И да, скорее всего, когда вы потратите 1 неделю на тонкую настройку ваших алгоритмов, вы должны получить что-то лучшее, чем полагаться на "реализации по умолчанию", которые предлагает Java.

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

Таким образом, реальный ответ будет следующим:

  • , чтобы измерить, сколько времени займет операция, чтобы определить ваши настоящие узкие места в вашей общей системе (например:должен ли типичный клиент использовать один поток для каждой страны, чтобы выбрать эти города, или меньшее количество потоков будет более полезным)
  • , если возможно, улучшите этот API REST, чтобы просто дать вамсписок городов прямо здесь

Короче говоря: вы должны сделать компромисс.Вы можете написать много пользовательского кода, чтобы получить лучшие результаты.Но никто не может сказать вам заранее о том, какую прибыль вы получите, и сколько «затрат» будет добавлено к вашему «бюджету» из-за «написания и поддержки более сложного кода с течением времени».

...