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
,而可以是 Future
、CompletableFuture
或 ListenableFuture
之一。
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
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 允许使用 Mono
、Flux
及其丰富的操作符集以声明的方式分解逻辑。此外,除了 @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
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 中,
Filter
和Servlet
是同步工作的,但 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