Spring MVC Async 和 WebFlux

1、概览

本文将带你了解 Spring Async 和 Spring WebFlux 之间的区别。

2、场景

本文分别用 Spring Async 和 Spring WebFlux 来实现一个简单的 Web 应用。

Web 请求会通过一个延迟时间为 200 毫秒的 Filter,然后 Controller 需要 500 毫秒来计算并返回结果。

最后使用 Apache ab 分别进行负载测试,并使用 JConsole 监控应用的行为。

3、Spring MVC Async

Spring 3.0 引入了 @Async 注解。@Async 的目标是允许应用在单独的线程上运行重负载的任务。此外,调用方可以等待结果(如果感兴趣)。因此,返回类型不能是void,而可以是 FutureCompletableFutureListenableFuture 之一。

Spring 3.2 引入了 org.springframework.web.context.request.async 包,它与 Servlet 3.0 一起为 Web 层带来了异步的支持。因此,自 Spring 3.2 起,@Async 可以在 @Controller@RestController 类中使用。

当客户端发起请求时,请求会经过过滤器链(filter chain)中所有匹配的过滤器(filter),直到到达 DispatcherServlet 实例。

然后,Servlet 会对请求进行异步调度。它通过调用 AsyncWebRequest#startAsync 将请求标记为已启动,将请求处理转移到 WebSyncManager 的实例,然后在不提交响应的情况下完成其工作。过滤器链也按相反的方向遍历执行到根节点。

WebAsyncManager 在其关联的 ExecutorService 中提交请求处理任务。每当结果准备就绪,它就会通知 DispatcherServlet 将响应返回给客户端。

4、Spring Async 实现

创建 Application 类 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  200ms 
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

最后,创建 AsyncController,有一个 /async_result 端点:

@RestController
public class AsyncController {
    @GetMapping("/async_result")
    @Async
    public CompletableFuture getResultAsyc(HttpServletRequest request) {
        // sleep 500 ms
        return CompletableFuture.completedFuture("Result is ready!");
    }
}

由于 getResultAsync 方法上注解了 @Async,该方法会在应用默认 ExecutorService 的单独线程中执行。不过,也可以为方法设置一个特定的 ExecutorService

运行应用,安装 Apache ab 或任何工具来模拟负载。向 async_result 端点发送大量并发请求,同时运行 JConsole 并将其连接到 Java 应用以监视该进程:

ab -n 1600 -c 40 localhost:8080/async_result

Async 测试结果和 JConsole

5、Spring WebFlux

Spring 5.0 引入了 WebFlux,以非阻塞方式支持响应式 Web。WebFlux 基于 Reactor API,是 Reactive Stream 的另一种出色实现。

Spring WebFlux 支持响应式背压和 Servlet 3.1+ 的非阻塞 I/O。因此,它可以在 Netty、Undertow、Jetty、Tomcat 或任何兼容 Servlet 3.1+ 的服务器上运行。

虽然所有服务器使用的线程管理和并发控制模型不尽相同,但只要支持非阻塞 I/O 和响应式背压,Spring WebFlux 就能正常工作。

Spring WebFlux 允许使用 MonoFlux 及其丰富的操作符集以声明的方式分解逻辑。此外,除了 @Controller 注解的端点外,还可以使用函数式端点。现在也可以在 Spring MVC 中使用这些端点。

6、Spring WebFlux 实现

对于 WebFlux 的实现,采用与异步相同的路径。

首先,创建 AsyncVsWebFluxApp

@SpringBootApplication
public class AsyncVsWebFluxApp {
    public static void main(String[] args) {
        SpringApplication.run(AsyncVsWebFluxApp.class, args);
    }
}

然后,编写 WebFilter 的实现 WebFluxFilter。在 filter 方法中故意延迟 200 毫秒,然后将请求传递给过滤器链:

@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

WebFlux 测试结果和 jconsole

7、区别

Spring Async 支持 Servlet 3.0 规范,但 Spring WebFlux 支持 Servlet 3.1+。这带来了许多不同之处:

  • Spring Async I/O 模型在与客户端通信时是阻塞的。这可能会对速度较慢的客户端造成性能问题。而,Spring WebFlux 则提供了非阻塞 I/O 模型。
  • 在 Spring Async 中,读取请求体或 Multipart 是阻塞的,而在 Spring WebFlux 中则是非阻塞的。
  • 在 Spring Async 中,FilterServlet 是同步工作的,但 Spring WebFlux 支持完全异步通信。
  • 与 Spring Async(如 Netty 和 Undertow)相比,Spring WebFlux 兼容更广泛的 Web/Application 服务器。

此外,Spring WebFlux 支持响应式背压(reactive backpressure),因此对 “如何应对快速生产者” 有更多的控制权。

Spring Flux 还具有明显的向函数式编码风格和声明式 API 分解的转变,这要归功于其背后的 Reactor API。

基于上述的区别,应该在项目中使用 Spring WebFlux 吗?根据系统所需的负载可扩展性或可用性,Spring Async 甚至 Spring MVC 可能是许多项目的正确选择。

在可扩展性方面,Spring Async 比同步的 Spring MVC 实现效果更好。而 Spring WebFlux 由于其反应式的特性,提供了弹性和更高的可用性。

8、总结

本文介绍了 Spring Async 和 Spring WebFlux,然后通过一个基本负载测试对它们进行了理论和实践上的比较。


Ref:https://www.baeldung.com/spring-mvc-async-vs-webflux