1. Введение
В этом руководстве мы рассмотрим аннотацию @Async
в Spring MVC, а затем познакомимся с Spring WebFlux. Наша цель — лучше понять разницу между ними.
2. Сценарий реализации
Здесь мы хотим выбрать сценарий, чтобы показать, как мы можем реализовать простое веб-приложение с каждым из этих API. Кроме того, нам особенно интересно узнать больше об управлении потоками и блокируемом или неблокирующем вводе-выводе в каждом случае.
Давайте выберем веб-приложение с одной конечной точкой, которое возвращает строковый результат. Дело в том, что запрос будет проходить через фильтр
с небольшой задержкой в 200 мс, а затем контроллеру
нужно 500 мс для расчета и возврата результата.
Далее мы собираемся смоделировать нагрузку с помощью Apache ab на обеих конечных точках и отслеживать поведение нашего приложения с помощью JConsole .
Возможно, стоит упомянуть, что в этой статье нашей целью является не сравнение этих двух API, а просто небольшой нагрузочный тест, чтобы мы могли отслеживать управление потоками .
3. Spring Асинхронный MVC
В Spring 3.0 появилась аннотация @Async
. Цель @Async
— позволить приложению выполнять тяжелые задания в отдельном потоке. Кроме того, вызывающий абонент может дождаться результата, если он заинтересован. Следовательно, возвращаемый тип не должен быть void
, и это может быть Future
, CompletableFuture
или ListenableFuture
.
Более того, в Spring 3.2 появился пакет org.springframework.web.context.request.async
, который вместе с Servlet 3.0 приносит удовольствие от асинхронного процесса на веб-уровень. Таким образом, начиная с Spring 3.2, @Async
можно использовать в классах, аннотированных как @Controller
или @RestController
.
Когда клиент инициирует запрос, он проходит через все соответствующие фильтры в цепочке фильтров, пока не достигнет экземпляра DispatcherServlet .
Затем сервлет позаботится об асинхронной отправке запроса. Он помечает запрос как запущенный путем вызова AsyncWebRequest#startAsync,
передает обработку запроса экземпляру WebSyncManager
и завершает свою работу, не фиксируя ответ. Цепочка фильтров также проходит в обратном направлении к корню. ``
WebAsyncManager
отправляет задание обработки запроса в связанный с ним ExecutorService
. Всякий раз, когда результат готов, он уведомляет DispatcherServlet
о возврате ответа клиенту.
4. Spring Асинхронная реализация
Давайте начнем реализацию с написания нашего класса приложения AsyncVsWebFluxApp
. Здесь @EnableAsync
делает волшебство включения асинхронности для нашего приложения Spring Boot:
@SpringBootApplication
@EnableAsync
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}
Затем у нас есть AsyncFilter
, реализующий javax.servlet.Filter.
Не забудьте смоделировать задержку в методе doFilter
:
@Component
public class AsyncFilter implements Filter {
...
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
// sleep for 200ms
filterChain.doFilter(servletRequest, servletResponse);
}
}
Наконец, мы разрабатываем наш AsyncController
с конечной точкой « /async_result
»:
@RestController
public class AsyncController {
@GetMapping("/async_result")
@Async
public CompletableFuture getResultAsyc(HttpServletRequest request) {
// sleep for 500 ms
return CompletableFuture.completedFuture("Result is ready!");
}
}
Из-за @Async
выше getResultAsync
этот метод выполняется в отдельном потоке в ExecutorService
приложения по умолчанию . Однако для нашего метода можно настроить конкретный ExecutorService
.
Время испытаний! Запустим приложение, установим Apache ab или любые инструменты для имитации нагрузки. Затем мы можем отправить кучу одновременных запросов через конечную точку «async_result». Мы можем запустить JConsole и подключить его к нашему java-приложению для мониторинга процесса:
ab -n 1600 -c 40 localhost:8080/async_result
5. Весенний WebFlux
Spring 5.0 представил WebFlux для поддержки реактивной сети неблокирующим образом. WebFlux основан на API-интерфейсе реактора, еще одной замечательной реализации реактивного потока .
Spring WebFlux поддерживает реактивное противодавление и Servlet 3.1+ с его неблокирующим вводом-выводом. Следовательно, его можно запустить на Netty, Undertow, Jetty, Tomcat или любом сервере, совместимом с Servlet 3.1+.
Хотя все серверы не используют одну и ту же модель управления потоками и параллелизма, Spring WebFlux будет работать нормально, если они поддерживают неблокирующий ввод-вывод и реактивное обратное давление.
Spring WebFlux позволяет нам декларативно декомпозировать логику с помощью Mono, Flux
и их богатых наборов операторов. Более того, у нас могут быть функциональные конечные точки помимо аннотированных @Controller
, хотя теперь мы также можем использовать их в Spring MVC .
6. Реализация Spring WebFlux
Для реализации WebFlux идем по тому же пути, что и async. Итак, сначала создадим AsyncVsWebFluxApp
:
@SpringBootApplication
public class AsyncVsWebFluxApp {
public static void main(String[] args) {
SpringApplication.run(AsyncVsWebFluxApp.class, args);
}
}
Затем напишем наш WebFluxFilter
, реализующий WebFilter.
Мы создадим преднамеренную задержку, а затем передадим запрос в цепочку фильтров:
@Component
public class WebFluxFilter implements org.springframework.web.server.WebFilter {
@Override
public Mono filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
return Mono
.delay(Duration.ofMillis(200))
.then(
webFilterChain.filter(serverWebExchange)
);
}
}
Наконец, у нас есть наш WebFluxController
. Он предоставляет конечную точку с именем «/flux_result»
и возвращает Mono<String>
в качестве ответа:
@RestController
public class WebFluxController {
@GetMapping("/flux_result")
public Mono getResult(ServerHttpRequest request) {
return Mono.defer(() -> Mono.just("Result is ready!"))
.delaySubscription(Duration.ofMillis(500));
}
}
Для теста мы используем тот же подход, что и в нашем примере асинхронного приложения. Вот пример результата для: ``
ab -n 1600 -c 40 localhost:8080/flux_result
7. В чем разница?
Spring Async поддерживает спецификации Servlet 3.0, но Spring WebFlux поддерживает Servlet 3.1+. Это приносит ряд отличий:
- Модель Spring Async I/O во время связи с клиентом блокируется. Это может вызвать проблемы с производительностью на медленных клиентах. С другой стороны, Spring WebFlux предоставляет неблокирующую модель ввода-вывода.
- Чтение тела запроса или частей запроса блокируется в Spring Async, тогда как в Spring WebFlux оно не блокируется.
- В Spring Async
фильтры
исервлеты
работают синхронно, но Spring WebFlux поддерживает полную асинхронную связь. - Spring WebFlux совместим с более широким диапазоном веб-серверов и серверов приложений, чем Spring Async, например Netty и Undertow.
Более того, Spring WebFlux поддерживает реактивное обратное давление, поэтому у нас больше контроля над тем, как мы должны реагировать на быстрых производителей, чем в Spring MVC Async и Spring MVC.
Spring Flux также имеет ощутимый сдвиг в сторону функционального стиля кодирования и декларативной декомпозиции API благодаря Reactor API.
Приводят ли все эти пункты к использованию Spring WebFlux? Ну, Spring Async или даже Spring MVC могут быть правильным ответом для многих проектов, в зависимости от желаемой масштабируемости нагрузки или доступности системы .
Что касается масштабируемости, Spring Async дает лучшие результаты, чем синхронная реализация Spring MVC. Spring WebFlux, благодаря своей реактивной природе, обеспечивает нам эластичность и более высокую доступность.
8. Заключение
В этой статье мы узнали больше о Spring Async и Spring WebFlux, затем провели их теоретическое и практическое сравнение с базовым нагрузочным тестом.
Как всегда, полный код для образца Async и образца WebFlux доступен на GitHub.