Многие современные веб-приложения проектируются таким образом, чтобы быть способными обрабатывать огромное количество запросов и при этом оставаться быстрыми в работе и отзывчивыми для конечного пользователя. Одним из способов как это достигается является применение асинхронной обработки данных.
Существует несколько вариантов её реализации. Например, один из подходов заключается в использовании Spring аннотации @Async. Эта аннотация позволяет разработчикам пометить метод как асинхронный, что означает, что он будет выполняться асинхронно в отдельном потоке. Таким образом, выполнение длительных операций не будет приводить к блокировке основного потока приложения и оно останется отзывчивым. Другой подход заключается в использовании Spring WebFlux, который предоставляет неблокирующую, управляемую событиями модель программирования для создания реактивных веб-приложений.
Типичное веб-приложение выполняет множество операций ввода-вывода, включая чтение и запись данных в базы данных, работу с внешними API и обработку файлов. Кроме того, сюда же могут входить: кэширование данных, взаимодействие с очередями сообщений и потоковая передача данных.
Обычно пользователи не задумываются, что все вышесказанное может находиться под капотом приложения и что оно так сложно устроено; для них ключевой характеристикой комфорта работы с приложением является его быстродействие.
В этой статье мы кратко рассмотрим как использование Spring WebFlux вместо аннотации @Async может привнести улучшения в быстродействие приложения.
Рассматривать мы будем на примере веб-приложения, которое обращается к внешнему сервису API для получения информации о запрашиваемом ip адресе в формате JSON. Само приложение, а также результаты тестирования его производительности, взяты из труда "Why You Should Use Spring WebFlux Instead of the @Async Annotation" автора Ganesh Sahu.
И так, в качестве внешнего АПИ будем использовать сервис, расположенный по адресу https://ipinfo.io/161.185.160.93/geo
GET запрос по нему возвращает:
{
"ip": "161.185.160.93",
"city": "New York City",
"region": "New York",
"country": "US",
"loc": "40.7143,-74.0060",
"org": "AS22252 The City of New York",
"postal": "10004",
"timezone": "America/New_York",
"readme": "https://ipinfo.io/missingauth"
}
Мы будем проводить стресс тестирование и зафиксируем показатели производительности, выполняя вызовы к этому API с использованием @Async и Web-flux.
Контроллер нашего приложения и сервис, который использует аннотацию @Async выглядят следующим образом:
@RestController
public class AsyncExternalCall {
@Autowired
AsyncService service;
@GetMapping(value = "/data", produces = MediaType.APPLICATION_JSON_VALUE)
public CompletableFuture<String> Data() {
return service.getDataAsync();
}
}
@Service
public class AsyncService {
private final RestTemplate restTemplate;
public AsyncService (RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@Async
public CompletableFuture<String> getDataAsync() {
String result = restTemplate.getForObject("https://ipinfo.io/161.185.160.93/geo", String.class);
return CompletableFuture.completedFuture(result);
}
}
Web-flux контроллер выглядит так:
@RestController
public class WebFluxExternalAPIController {
private final WebClient webClient;
public ExternalAPIController(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://ipinfo.io").build();
}
@GetMapping(value = "/data", produces = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> getData() {
return webClient.get()
.uri("/161.185.160.93/geo")
.retrieve()
.bodyToMono(String.class);
}
}
Чтобы смоделировать большое количество запросов и оценить время ответа и пропускную способность каждого из контроллеров (сервисов) при различных условиях, был применен инструмент нагрузочного тестирования - Apache JMeter (генерировалась нагрузка в 100 запросов в секунду, каждый сервис был запущен на своей отдельной виртуальной машине)
Ниже приведена статистика, полученная при использовании JMeter:



Как видно из статистики, сервис на Webflux превзошел сервис на @Async (4021 против 6684 в среднем, 8090 против 11542 в максимуме; чем меньше значение, тем лучше).
Также было проведено изолированное тестирование с помощью Postman, которое показало значительное сокращение времени отклика сервиса Webflux по сравнению с сервисом на @Async.
Подводя итог, хочется также отметить, что помимо более эффективной асинхронной обработки, Spring WebFlux предлагает готовую интеграцию с библиотекой Reactor.
Reactor - это мощный набор инструментов для реактивного программирования, включающий широкий спектр операторов, планировщиков и других функций, которые могут упростить написание отзывчивого кода. С помощью Spring WebFlux и Reactor можно создавать приложения, которые не только очень эффективны, но и гибки и просты в обслуживании. А еще Spring WebFlux можно использовать как с Java, так и с Kotlin. Это делает его отличным выбором для для создания современных высокопроизводительных приложений на удобном для вас языке.