1. введение
В этом уроке мы рассмотрим аннотацию @Async в Spring MVC, а затем познакомимся с Spring Web Flux. Наша цель-лучше понять разницу между этими двумя понятиями.
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 приносит радость асинхронного процесса на веб-уровень. Таким образом, начиная с весны 3.2, @Async может использоваться в классах, аннотированных как @Controller или @RestController .
Когда клиент инициирует запрос, он проходит через все соответствующие фильтры в цепочке фильтров, пока не достигнет экземпляра DispatcherServlet .
Затем сервлет позаботится об асинхронной отправке запроса. Он помечает запрос как начатый вызовом AsyncWebRequest#startAsync, передает обработку запроса экземпляру WebSyncManager и завершает свою работу без фиксации ответа. Цепочка фильтров также проходит в обратном направлении к корню.
WebAsyncManager отправляет задание обработки запроса в связанный с ним ExecutorService . Всякий раз, когда результат готов, он уведомляет DispatcherServlet о возврате ответа клиенту.
4. Весенняя Асинхронная реализация
Давайте начнем реализацию с написания нашего класса приложения AsyncVsWebFluxApp . H ere, @EnableAsync делает волшебство включения асинхронности для нашего приложения Spring Boot:
@SpringBootApplication @EnableAsync public class AsyncVsWebFluxApp { public static void main(String[] args) { SpringApplication.run(AsyncVsWebFluxApp.class, args); } }
Тогда у нас есть Асинхронный фильтр , который реализует javax.servlet.Фильтр. Не забудьте смоделировать задержку в методе 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); } }
Наконец, мы разрабатываем наш Асинхронный контроллер с конечной точкой ” /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”. Мы можем выполнить консоль и прикрепить ее к нашему java-приложению для мониторинга процесса:
ab -n 1600 -c 40 localhost:8080/async_result
5. Поток пружинного полотна
Spring 5.0 представила WebFlux для поддержки реактивной сети неблокирующим способом. Web Flux основан на API реактора, просто еще одна потрясающая реализация реактивного потока .
Поток пружинного полотна поддерживает реактивное противодавление и Сервлет 3.1+ с его неблокирующим вводом-выводом. Следовательно, он может быть запущен на Netty, Undertow, Jetty, Tomcat или любом сервере, совместимом с сервлетом 3.1+.
Хотя все серверы не используют одну и ту же модель управления потоками и управления параллелизмом, Spring WebFlux будет работать нормально, если они поддерживают неблокирующий ввод-вывод и реактивное противодавление.
Spring Web Flux позволяет нам декомпозировать логику декларативным способом с помощью Mono, Flux, и их богатых наборов операторов. Кроме того, у нас могут быть функциональные конечные точки помимо аннотированных @Controller , хотя теперь мы также можем использовать их в Spring MVC .
6. Реализация весеннего веб-потока
Для реализации Web Flux мы идем по тому же пути , что и async. Итак, сначала давайте создадим AsyncVsWebFluxApp :
@SpringBootApplication public class AsyncVsWebFluxApp { public static void main(String[] args) { SpringApplication.run(AsyncVsWebFluxApp.class, args); } }
Тогда давайте напишем наш Веб-фильтр потока , который реализует Веб-фильтр. Мы создадим преднамеренную задержку, а затем передадим запрос в цепочку фильтров:
@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) ); } }
Наконец-то у нас есть наш Веб-контроллер потока . Он предоставляет конечную точку с именем “/flux_result” и возвращает Mono в качестве ответа:
@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 поддерживает спецификации сервлета 3.0, но Spring Web Flux поддерживает сервлет 3.1+. Это приносит ряд отличий:
- Модель асинхронного ввода-вывода Spring во время ее взаимодействия с клиентом блокируется. Это может вызвать проблемы с производительностью при работе с медленными клиентами. С другой стороны, Spring Web Flux обеспечивает неблокирующую модель ввода-вывода.
- Чтение тела запроса или частей запроса блокируется в Spring Async, в то время как в Spring WebFlux оно не блокируется.
- В Spring Async Filter sand Servlet s работают синхронно, но Spring WebFlux поддерживает полную асинхронную связь.
- Spring Web Flux совместим с более широким диапазоном серверов веб-приложений, чем Spring Async, например Netty и Undertow.
Кроме того, Spring WebFlux поддерживает реактивное противодавление, поэтому у нас больше контроля над тем, как мы должны реагировать на быстрые производители, чем у Spring MVC Async и Spring MVC.
Spring Flux также имеет ощутимый сдвиг в сторону функционального стиля кодирования и декларативной декомпозиции API благодаря API Reactor, стоящему за ним.
Все ли эти элементы приводят нас к использованию Spring Web Flux? Ну, Spring Async или даже Spring MVC могут быть правильным ответом на многие проекты, в зависимости от желаемой масштабируемости нагрузки или доступности системы .
Что касается масштабируемости, Spring Async дает нам лучшие результаты, чем синхронная реализация Spring MVC. Поток пружинного полотна, благодаря своей реактивной природе, обеспечивает нам эластичность и более высокую доступность.
8. Заключение
В этой статье мы узнали больше о Spring Async и Spring Web Flux, а затем провели их теоретическое и практическое сравнение с базовым нагрузочным тестом.
Как всегда, полный код для образца Async и образца Web Flux доступен на GitHub.