Spring WebFlux与Spring MVC

Spring WebFlux 是对 Spring Boot 项目中传统 Spring MVC 部分的一种更新迭代,主要是为了解决现代 Web 应用在高并发和低延迟场景下的性能瓶颈。

Spring WebFlux的性能是强于Spring MVC的,但是关于Spring WebFlux的控制更加复杂,所以在简单场景下还是使用Spring MVC。

1.WebFlux 是对 Spring MVC 的强化替代

  • 架构替代

    • Spring MVC 使用的是基于 Servlet 规范的阻塞式模型(一个请求分配一个线程)。
    • WebFlux 是一个完全基于非阻塞 I/O 的框架,底层可以脱离 Servlet 容器(如使用 Netty),也支持运行在 Servlet 容器上。
  • 编程模型替代

    • Spring MVC 使用同步(阻塞)模型,直接返回对象(如 String 或 ResponseEntity)。
    • WebFlux 使用响应式(Reactive)模型,返回类型是 Mono 或 Flux,实现非阻塞操作。

2.Spring MVC 和 WebFlux 的共存

2.1.Spring MVC 和 Spring WebFlux 的处理路径分离机制

在一个同时引入了 Spring MVCSpring WebFlux 的项目中,Spring 通过请求路径的映射机制以及底层的配置来区分应该将请求交给谁处理(DispatcherServletWebHandler)。

(1)Spring MVC 的工作机制

  • Servlet 容器支持:Spring MVC 基于 Servlet API 和传统线程池模型运行,默认使用 DispatcherServlet
  • URL 路径映射DispatcherServlet 依赖 Handler Mapping(如 @RequestMapping)来将请求路由到对应的控制器(Controller)。
  • 处理阻塞模型:处理请求时会为每个 HTTP 请求分配一个线程,等待操作完成后再返回响应。

(2)Spring WebFlux 的工作机制

  • 事件驱动模型:Spring WebFlux 不依赖 Servlet 容器,可以运行在基于事件驱动的非阻塞网络框架(如 Reactor Netty)上。也可以在 Servlet 容器上运行,通过适配器将事件驱动与 Servlet API 兼容。
  • URL 路径映射:WebFlux 的 WebHandler 负责处理请求,使用类似 @RequestMapping 的注解来映射路径。
  • 响应式非阻塞:WebFlux 使用 Reactor 库中的 MonoFlux 来处理异步数据流。

2.2.二者混用之后spring如何判断请求交给哪种架构的处理器处理

  • 返回值类型决定了处理器

    • 如果控制器方法返回阻塞类型(如 StringResponseEntity),请求会由 Spring MVC 的 DispatcherServlet 处理。
    • 如果控制器方法返回响应式类型(如 MonoFlux),请求会由 Spring WebFlux 的 WebHandler 处理。
  • Spring 的默认行为限制

    • Spring MVC 控制器Spring WebFlux 控制器在逻辑上是完全独立的。
    • 同一个控制器(@RestController 类)不能混用阻塞式方法和响应式方法。否则,Spring 不知道该为这个控制器注册到哪个处理器中(因为一个控制器只能归属到一个处理器)。

当在一个项目中混用两种架构时,你必须在不同的 Controller 中分离 Spring MVC 和 Spring WebFlux 的请求处理。


2.3. Spring MVC 和 Spring WebFlux 的主要区别

特性 Spring MVC Spring WebFlux
编程模型 基于阻塞式编程(Servlet 规范) 基于非阻塞式响应式编程(Reactive Streams)
默认运行环境 Servlet 容器(如 Tomcat、Jetty) 默认运行在 Reactor Netty 上,也可运行在 Servlet 容器上
核心组件 DispatcherServlet,通过线程池处理请求 WebHandler,通过事件驱动处理请求
数据返回方式 同步返回结果(如 StringModelAndView 使用 MonoFlux 返回响应式数据流
性能 为每个请求分配线程,线程阻塞等待操作完成 单线程事件循环,适合高并发、长连接的场景
典型应用场景 传统的 Web 应用开发,如管理系统、企业级应用 实时推送、高并发 API 服务、微服务架构

2.4. Spring MVC 和 WebFlux 的架构对比

(1)Spring MVC 的架构

DispatcherServlet

  • 是 Spring MVC 的核心组件,负责接收所有 HTTP 请求并将其分发到适当的控制器。
  • 配合 Handler Mapping 和 Handler Adapter,将 URL 路由到 @Controller
  • 运行在 Servlet 容器上(如 Tomcat),依赖线程池为每个请求分配一个线程。

工作流程

  1. 用户发送 HTTP 请求。
  2. DispatcherServlet 拦截请求。
  3. 根据 URL 调用合适的控制器方法。
  4. 控制器返回视图(如 ModelAndView 或 JSON 数据)。
  5. 线程等待操作完成后返回结果。

(2)Spring WebFlux 的架构

WebHandler

  • 是 Spring WebFlux 的核心组件,处理非阻塞式请求。
  • 使用 RouterFunction 或注解(如 @Controller@RequestMapping)映射路径。
  • 默认运行在 Reactor Netty 上,但可通过适配器在 Servlet 容器上运行。

工作流程

  1. 用户发送 HTTP 请求。
  2. WebHandler 拦截请求。
  3. 将请求异步分发到控制器(Controller)。
  4. 控制器返回 MonoFlux,数据流被处理后发送响应。
  5. 请求线程不阻塞,可以处理其他任务。

3.为什么WebFlux架构相较于Spring MVC可以做到高并发、低延迟?

3.1. 核心架构差异

Spring MVC 的阻塞模型

  • 基于 Servlet API线程池模型

  • 每个 HTTP 请求都会分配一个线程,线程会持续等待处理完成,直到控制器方法返回结果。

  • 在处理过程中,如果遇到 I/O 操作(例如数据库查询或调用外部服务),线程会被阻塞,直到 I/O 操作完成。

瓶颈来源

  • 线程资源是有限的,线程池的大小直接决定了并发能力。

  • 如果线程池被耗尽(如大量请求处于等待 I/O 状态),新的请求会被阻塞,延迟会显著增加。

Spring WebFlux 的非阻塞模型

  • 基于 Reactive Streams 规范,使用 Reactor 库实现非阻塞式事件驱动架构。
  • 通过事件循环和少量线程处理所有请求,而不会为每个请求分配一个独立线程。
  • I/O 操作(如数据库查询、外部 API 调用)不会阻塞线程,而是通过事件机制在操作完成后触发响应。

优点

  • 无需为每个请求分配线程,能够支持大量并发请求。
  • 线程不被阻塞,可以自由处理其他任务,从而提高资源利用率。

3.2. 并发处理能力

特性 Spring MVC Spring WebFlux
线程模型 每个请求分配一个线程,线程池大小有限 少量线程通过事件循环处理大量并发请求
阻塞操作 线程被阻塞等待 I/O 完成 I/O 操作不会阻塞线程,响应完成后通过事件触发
并发能力 受限于线程池大小和上下文切换的开销 可以支持更高的并发量,通常是 MVC 的 10 倍以上

举例说明:

  • 假设一个应用需要处理 10,000 个并发请求:
    • Spring MVC
      • 如果线程池大小为 200,每个请求平均处理时间为 500ms,则只能同时处理 200 个请求,剩下的 9,800 个请求会被阻塞,延迟显著增加。
    • Spring WebFlux
      • 使用非阻塞模型,同样的 200 个线程可以通过事件循环同时处理这 10,000 个请求。

3.3. 延迟优化能力

Spring MVC 的延迟问题

  • 每个请求的处理路径是串行的,线程在等待操作完成时无法处理其他请求。

  • 如果某些请求耗时较长(如外部服务慢响应),会拖累整个系统的吞吐量。

Spring WebFlux 的低延迟优势

  • 通过异步非阻塞机制,WebFlux 可以避免线程等待:
    • 当某个请求需要等待 I/O 时(如数据库查询、远程调用),线程会被释放用于其他任务。
    • 一旦 I/O 完成,WebFlux 会通过事件通知机制继续处理该请求,减少总体延迟。

3.4. 资源利用效率

Spring MVC

  • 线程池限制:线程是宝贵资源,线程池大小通常不能太大,否则线程上下文切换会带来额外开销。

  • 内存消耗:每个线程消耗一定的内存(通常几百 KB 到几 MB),高并发时可能导致内存不足。

Spring WebFlux

  • 少量线程:通过事件循环机制,同样的线程可以处理多个请求,大大降低线程需求。
  • 更少的内存消耗:由于线程数少,内存开销显著降低,系统资源使用更高效。

3.5. 适用场景的对比

场景 Spring MVC(阻塞模型) Spring WebFlux(非阻塞模型)
普通 Web 应用 非常适合,用于阻塞式请求/响应场景,例如传统的表单提交、数据展示等 可以使用,但性能优势不明显
高并发 API 服务 性能瓶颈明显,不适合处理大量并发请求 非常适合高并发和低延迟 API 服务,例如实时推送、微服务架构
实时推送(如 WebSocket) 不适合,线程阻塞会导致延迟增加 支持 WebSocket 和 SSE,延迟低,资源使用高效
异步任务处理 需要额外的异步机制(如 CompletableFuture)才能实现 天生支持异步操作,通过 MonoFlux 简化开发

3.6. 为什么选择 Spring MVC 或 Spring WebFlux?

选择 Spring MVC 的场景

  • 项目是传统 Web 应用,阻塞式模型即可满足需求。

  • 并发量较低,或者主要是 CPU 密集型操作(如复杂计算、业务逻辑处理)。

  • 不需要支持实时推送、高并发的 API。

选择 Spring WebFlux 的场景

  • 应用需要高并发支持(如 10,000+ QPS)。
  • I/O 密集型应用(如调用外部服务、处理大量数据库查询)。
  • 需要异步任务支持或实时推送功能(如 WebSocket)。

4.Spring WebFlux架构中得事件循环机制

事件循环机制(Event Loop) 是一种非阻塞的处理模型,它的核心思想是通过一个或少量线程不断地轮询事件队列中的任务,并触发事件来处理任务,而不是为每个任务分配一个独立线程。这种机制主要依赖于回调(Callback)或事件驱动的编程模型。


4.1. 什么是事件循环机制

核心概念

  • 单线程或少量线程

    • 一个线程循环运行,监听事件队列中的任务。

    • 线程不会阻塞,而是通过异步回调的方式处理任务。

  • 任务分为两类

    • 同步任务:能够立即完成的任务(如简单计算)。

    • 异步任务:需要等待外部资源(如 I/O 操作完成)。

  • 异步操作的分离

    • 异步任务不会阻塞事件循环,而是将任务挂起(放入队列)。

    • 当异步任务完成(如文件读取完成、数据库查询完成),会触发对应的事件,再次将任务加入事件队列中,供事件循环处理。

运作方式

  • 事件循环会持续不断地:

    1. 检查是否有新的任务。

    2. 立即处理同步任务。

    3. 对于异步任务,触发事件并调用相关回调函数。

4.2. 为什么事件循环机制可以让同样的线程处理多个请求

传统线程模型的缺点(如 Spring MVC)

  • 每个请求分配一个独立的线程。

  • 如果线程在处理 I/O(如数据库查询或 HTTP 调用),它会进入等待状态,直到 I/O 操作完成。

  • 线程被阻塞期间无法处理其他任务,导致资源浪费。

事件循环的优点

  • 非阻塞 I/O:线程不会等待 I/O 完成,而是立即返回,将任务挂起。

  • 统一调度:少量线程通过事件循环机制调度多个任务,避免线程的闲置和上下文切换。

举例:

假设有以下两个任务:

  • 任务 A:从数据库读取数据(需要 2 秒)。

  • 任务 B:简单计算任务(需要 10 毫秒)。

传统线程模型

  1. 任务 A 被分配一个线程,线程会等待 2 秒,无法处理其他任务。

  2. 任务 B 被分配另一个线程,但需要消耗额外的线程资源。

事件循环模型

  1. 任务 A 提交一个 I/O 操作请求,线程将任务挂起,不阻塞,继续监听其他任务。

  2. 任务 B 被事件循环立即处理,完成后释放线程。

  3. 当任务 A 的 I/O 操作完成时,会触发一个事件,线程继续处理任务 A 的后续逻辑。

同一个线程在 2 秒内完成了任务 A 和任务 B,而不需要同时占用两个线程。


4.3. WebFlux 中事件循环的实现

Spring WebFlux 的事件循环是基于 Reactor 库和 Reactive Streams 规范,默认使用 Netty 作为底层支持。其事件循环机制主要体现在以下方面:

关键组件

  • Reactor Netty 的 Event Loop

    • Reactor Netty 是 WebFlux 默认的网络引擎,使用少量线程通过事件循环处理请求。
    • 每个线程负责处理多个请求,不会阻塞线程。
  • Reactor Scheduler

    • Reactor 提供了调度器(Scheduler)来管理异步任务的执行。
    • 它将计算任务、I/O 操作分离到不同的线程池中,确保事件循环专注于任务调度,而复杂计算或 I/O 操作不会阻塞事件循环。

4.4. 为什么事件循环可以支持高并发、低延迟

核心优势

  • 线程复用

    • 一个线程可以在短时间内处理多个请求。

    • 避免了传统线程模型中频繁创建线程、切换上下文的开销。

  • 非阻塞 I/O

    • 异步任务挂起后,线程不会等待,而是继续处理其他任务,直到异步任务完成。

  • 资源节省

    • 事件循环机制通常只需要少量线程,显著降低内存和 CPU 消耗。

对比示例

假设需要处理 10,000 个并发请求:

  • 传统线程模型(Spring MVC)

    • 每个请求分配一个线程,假设线程池大小为 500。

    • 当线程池耗尽时,其他请求会被阻塞,延迟增加。

    • 系统内存被占用,线程切换开销显著。

  • 事件循环模型(Spring WebFlux)

    • 事件循环线程数为 4,每个线程处理多个请求。

    • 请求不会阻塞线程,I/O 操作完成后触发事件。

    • 系统资源得到高效利用,延迟显著降低。


4.5. 实际例子:高并发场景

使用 Spring MVC

@RestController
public class MvcController {
    @GetMapping("/data")
    public String getData() {
        // 模拟 I/O 操作
        try {
            Thread.sleep(2000); // 模拟等待 2 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Data";
    }
}
  • 每个请求都会阻塞一个线程。

  • 并发量增加时,线程池会被耗尽,导致请求延迟增加。

使用 Spring WebFlux

@RestController
public class WebFluxController {
    @GetMapping("/data")
    public Mono getData() {
        // 模拟异步操作
        return Mono.fromCallable(() -> {
            Thread.sleep(2000); // 模拟等待 2 秒
            return "Data";
        }).subscribeOn(Schedulers.boundedElastic());
    }
}
  • 请求被挂起,线程不会阻塞。

  • I/O 操作完成后,事件触发任务处理,支持更高的并发。

5.WebFlux 替代 Spring MVC 的主要挑战

代码改造

Spring MVC

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String sayHello() {
        return "Hello, Spring MVC!";
    }
}

WebFlux

@RestController
public class HelloController {
    @GetMapping("/hello")
    public Mono sayHello() {
        return Mono.just("Hello, WebFlux!");
    }
}
  • 传统 Spring MVC 的控制器代码需要改写为响应式风格,返回类型从 String 或 ResponseEntity 改为 Mono 或 Flux

学习曲线

  • 团队需要学习响应式编程(Reactive Programming),熟悉 Mono 和 Flux 的操作。

阻塞组件的兼容性

  • WebFlux 在引入阻塞组件(如传统 JDBC 驱动)时,可能会破坏非阻塞特性,因此需要替换为响应式驱动(如 R2DBC)。

你可能感兴趣的:(WebFlux,spring)