1. Обзор
В этом руководстве мы собираемся сравнить две реализации веб-клиента Spring — RestTemplate
и новую реактивную альтернативу Spring 5 WebClient
.
2. Блокирующий и неблокирующий клиент
В веб-приложениях обычно требуется выполнять вызовы HTTP к другим службам. Итак, нам нужен веб-клиент.
2.1. Блокирующий клиент RestTemplate
В течение долгого времени Spring предлагал RestTemplate
в качестве абстракции веб-клиента. Под капотом RestTemplate
использует Java Servlet API, основанный на модели «поток на запрос».
Это означает, что поток будет заблокирован до тех пор, пока веб-клиент не получит ответ. Проблема с блокирующим кодом связана с тем, что каждый поток потребляет некоторое количество памяти и циклов процессора.
Давайте рассмотрим наличие большого количества входящих запросов, ожидающих какой-то медленной службы, необходимой для получения результата.
Рано или поздно запросы, ожидающие результатов, накапливаются. Следовательно, приложение создаст много потоков, которые исчерпают пул потоков или займут всю доступную память. Мы также можем столкнуться со снижением производительности из-за частого переключения контекста ЦП (потока).
2.2. WebClient
Неблокирующий клиент
С другой стороны, WebClient
использует асинхронное неблокирующее решение, предоставляемое инфраструктурой Spring Reactive.
В то время как RestTemplate
использует поток вызывающей стороны для каждого события (вызов HTTP), WebClient
создаст что-то вроде «задачи» для каждого события. За кулисами Reactive framework поставит эти «задачи» в очередь и выполнит их только тогда, когда будет доступен соответствующий ответ.
Фреймворк Reactive использует архитектуру, управляемую событиями. Он предоставляет средства для составления асинхронной логики через API Reactive Streams . В результате реактивный подход может обрабатывать больше логики при использовании меньшего количества потоков и системных ресурсов по сравнению с синхронным/блокирующим методом.
WebClient
является частью библиотеки Spring WebFlux . Таким образом, мы также можем писать клиентский код, используя функциональный, гибкий API с реактивными типами ( Mono
и Flux
) в качестве декларативной композиции.
3. Пример сравнения
Чтобы продемонстрировать различия между этими двумя подходами, нам потребуется запустить тесты производительности с множеством одновременных клиентских запросов.
Мы бы увидели значительное снижение производительности при использовании метода блокировки после определенного количества параллельных клиентских запросов.
Однако реактивный/неблокирующий метод должен давать постоянную производительность независимо от количества запросов.
В этой статье мы реализуем две конечные точки REST, одну с помощью RestTemplate
, а другую с помощью WebClient
. Их задача — вызвать еще один медленный веб-сервис REST, который возвращает список твитов.
Для начала нам понадобится стартовая зависимость Spring Boot WebFlux :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
А вот и наша конечная точка REST с медленным сервисом:
@GetMapping("/slow-service-tweets")
private List<Tweet> getAllTweets() {
Thread.sleep(2000L); // delay
return Arrays.asList(
new Tweet("RestTemplate rules", "@user1"),
new Tweet("WebClient is better", "@user2"),
new Tweet("OK, both are useful", "@user1"));
}
3.1. Использование RestTemplate
для вызова медленной службы
Давайте теперь реализуем другую конечную точку REST, которая будет вызывать нашу медленную службу через веб-клиент.
Во- первых, мы будем использовать RestTemplate
:
@GetMapping("/tweets-blocking")
public List<Tweet> getTweetsBlocking() {
log.info("Starting BLOCKING Controller!");
final String uri = getSlowServiceUri();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<List<Tweet>> response = restTemplate.exchange(
uri, HttpMethod.GET, null,
new ParameterizedTypeReference<List<Tweet>>(){});
List<Tweet> result = response.getBody();
result.forEach(tweet -> log.info(tweet.toString()));
log.info("Exiting BLOCKING Controller!");
return result;
}
Когда мы вызываем эту конечную точку, из-за синхронного характера RestTemplate
код заблокируется в ожидании ответа от нашего медленного сервиса. Остальной код этого метода будет выполняться только после получения ответа.
Вот что мы увидим в логах:
Starting BLOCKING Controller!
Tweet(text=RestTemplate rules, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)
Exiting BLOCKING Controller!
3.2. Использование WebClient
для вызова медленной службы
Во-вторых, давайте воспользуемся WebClient
для вызова медленного сервиса:
@GetMapping(value = "/tweets-non-blocking",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Tweet> getTweetsNonBlocking() {
log.info("Starting NON-BLOCKING Controller!");
Flux<Tweet> tweetFlux = WebClient.create()
.get()
.uri(getSlowServiceUri())
.retrieve()
.bodyToFlux(Tweet.class);
tweetFlux.subscribe(tweet -> log.info(tweet.toString()));
log.info("Exiting NON-BLOCKING Controller!");
return tweetFlux;
}
В этом случае WebClient
возвращает издателя Flux
, и выполнение метода завершается. Как только результат будет доступен, издатель начнет рассылать твиты своим подписчикам.
Обратите внимание, что клиент (в данном случае веб-браузер), вызывающий эту конечную точку /tweets-non-blocking
, также будет подписан на возвращаемый объект Flux .
Давайте посмотрим на журнал на этот раз:
Starting NON-BLOCKING Controller!
Exiting NON-BLOCKING Controller!
Tweet(text=RestTemplate rules, username=@user1)
Tweet(text=WebClient is better, username=@user2)
Tweet(text=OK, both are useful, username=@user1)
Обратите внимание, что этот метод конечной точки завершен до получения ответа.
4. Вывод
В этой статье мы рассмотрели два разных способа использования веб-клиентов в Spring.
RestTemplate
использует Java Servlet API и поэтому является синхронным и блокирующим.
И наоборот, WebClient
является асинхронным и не будет блокировать исполняемый поток, ожидая ответа. Уведомление будет произведено только тогда, когда ответ будет готов.
RestTemplate
по- прежнему будет использоваться. Но в некоторых случаях неблокирующий подход использует гораздо меньше системных ресурсов по сравнению с блокирующим. Таким образом, WebClient
является предпочтительным выбором в этих случаях.
Все фрагменты кода, упомянутые в статье, можно найти на GitHub .