Перейти к основному содержимому

Spring MVC Async против Spring WebFlux

· 6 мин. чтения

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

./d866b67c19b4adf8a71beacfa842eeac.png

./fec9dc0bac3a315bd4ab7252b3dbfc8e.png

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

./37db5fcbc0e167af9c2060a7782d5073.png

./95cb4e5823a484160ebadd98ad002eeb.png

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.