高并发场景下 Spring MVC + 虚拟线程 vs WebFlux 选型对比

在 Java 21 正式发布虚拟线程(Virtual Threads)之后,高并发编程的范式正在经历一场深刻变革。本文将从架构原理、性能模型、代码实践三个维度,深入对比 Spring MVC + 虚拟线程与 Spring WebFlux 的选型策略。


一、背景:为什么会有这场对比?

传统的 Spring MVC 基于 Servlet 容器(Tomcat),采用一请求一线程模型,线程数受限于操作系统线程开销(通常约 1MB 栈空间),在 I/O 密集型场景下容易成为瓶颈。

Spring WebFlux 引入响应式编程 范式,基于 Reactor + Netty,通过事件循环(Event Loop)用少量线程处理大量并发,但其 Mono/Flux 链式 API 学习曲线陡峭,调试困难,生态兼容性差。

Java 21 的虚拟线程提供了一条中间路线:保持传统同步编程模型,但线程由 JVM 调度,轻量到可以轻松创建百万级。

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                     并发模型演进时间线                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  2005          2013              2017            2023           │
│   │             │                 │               │             │
│   ▼             ▼                 ▼               ▼             │
│ Servlet      Servlet 3.1      WebFlux        Virtual Threads   │
│ 一请求一线程   异步 Servlet     响应式编程      虚拟线程          │
│ 线程昂贵       NIO支持         Mono/Flux       百万级轻量线程     │
│ ~2000并发      ~5000并发        ~50000并发      ~1000000并发     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

二、架构原理对比

2.1 线程模型

复制代码
┌─────────────────── Spring MVC (Platform Thread) ───────────────────┐
│                                                                     │
│  Request ──▶ Tomcat Thread Pool (200 threads) ──▶ Blocking I/O     │
│                  │                              ──▶ DB Wait         │
│                  │                              ──▶ HTTP Call Wait   │
│                  ▼                                                  │
│           线程被阻塞,无法处理新请求                                    │
│           饱和后新请求排队等待                                         │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────── Spring WebFlux (Event Loop) ────────────────────┐
│                                                                     │
│  Request ──▶ Netty Event Loop (CPU核心数 x 2) ──▶ Non-blocking    │
│                  │                              ──▶ Callback Chain │
│                  ▼                                                  │
│           线程永不阻塞,通过回调推进                                    │
│           但回调链复杂,调试困难                                       │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────── Spring MVC + Virtual Thread ────────────────────┐
│                                                                     │
│  Request ──▶ Virtual Thread (百万级) ──▶ Blocking I/O (伪阻塞)     │
│                  │                     ──▶ 自动 unmount/mount       │
│                  ▼                                                  │
│           虚拟线程在 I/O 时自动让出载体线程                              │
│           编程模型与传统同步代码完全一致                                 │
└─────────────────────────────────────────────────────────────────────┘

2.2 关键差异对照表

维度 Spring MVC + 平台线程 Spring WebFlux Spring MVC + 虚拟线程
线程模型 平台线程池(~200) Event Loop(CPU×2) 虚拟线程(百万级)
编程范式 同步阻塞 异步响应式 同步阻塞(伪阻塞)
I/O 处理 阻塞等待 非阻塞 + 回调 自动挂起/恢复
代码复杂度 ⭐ 低 ⭐⭐⭐⭐ 高 ⭐ 低
调试难度 简单 困难(堆栈难读) 简单
最大并发 ~2,000 ~50,000+ ~1,000,000+
生态兼容 完全兼容 部分兼容(需响应式驱动) 完全兼容

三、代码实战对比

3.1 Spring MVC + 虚拟线程

启用虚拟线程(Spring Boot 3.2+)
yaml 复制代码
# application.yml
spring:
  threads:
    virtual:
      enabled: true
java 复制代码
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

就这么简单!开启配置后,Spring Boot 自动将 Tomcat 的请求处理线程替换为虚拟线程。

Controller 层 --- 同步风格,异步性能
java 复制代码
@RestController
@RequestMapping("/api")
public class OrderController {

    private final OrderService orderService;
    private final InventoryClient inventoryClient;
    private final PaymentClient paymentClient;

    public OrderController(OrderService orderService,
                           InventoryClient inventoryClient,
                           PaymentClient paymentClient) {
        this.orderService = orderService;
        this.inventoryClient = inventoryClient;
        this.paymentClient = paymentClient;
    }

    @GetMapping("/orders/{id}")
    public OrderDetail getOrder(@PathVariable Long id) {
        // 每个阻塞调用都会自动挂起虚拟线程,释放载体线程
        Order order = orderService.findById(id);              // 阻塞调用 1
        Inventory inv = inventoryClient.checkStock(id);       // 阻塞调用 2 (HTTP)
        PaymentStatus ps = paymentClient.getPaymentStatus(id);// 阻塞调用 3 (HTTP)

        return new OrderDetail(order, inv, ps);
    }

    @PostMapping("/orders")
    @ResponseStatus(HttpStatus.CREATED)
    public Order createOrder(@RequestBody CreateOrderRequest req) {
        // 传统同步写法,但底层自动享受非阻塞优势
        inventoryClient.reserve(req.getProductId(), req.getQuantity());
        PaymentResult payment = paymentClient.charge(req.getAmount());
        return orderService.create(req, payment.getTxnId());
    }
}
使用 CompletableFuture 并发编排
java 复制代码
@GetMapping("/orders/{id}/full")
public OrderFullDetail getFullOrder(@PathVariable Long id) {
    // 虚拟线程 + CompletableFuture:兼顾可读性与并发
    CompletableFuture<Order> orderFuture =
        CompletableFuture.supplyAsync(() -> orderService.findById(id));

    CompletableFuture<List<Logistics>> logisticsFuture =
        CompletableFuture.supplyAsync(() -> logisticsService.track(id));

    CompletableFuture<Review> reviewFuture =
        CompletableFuture.supplyAsync(() -> reviewService.getByOrder(id));

    // 等待所有结果(每个 supplyAsync 都运行在独立的虚拟线程上)
    CompletableFuture.allOf(orderFuture, logisticsFuture, reviewFuture).join();

    return new OrderFullDetail(
        orderFuture.join(),
        logisticsFuture.join(),
        reviewFuture.join()
    );
}

3.2 Spring WebFlux

Controller 层 --- 响应式风格
java 复制代码
@RestController
@RequestMapping("/api")
public class OrderController {

    private final OrderService orderService;
    private final InventoryClient inventoryClient;
    private final PaymentClient paymentClient;

    public OrderController(OrderService orderService,
                           InventoryClient inventoryClient,
                           PaymentClient paymentClient) {
        this.orderService = orderService;
        this.inventoryClient = inventoryClient;
        this.paymentClient = paymentClient;
    }

    @GetMapping("/orders/{id}")
    public Mono<OrderDetail> getOrder(@PathVariable Long id) {
        // Mono.zip 并发编排
        return Mono.zip(
                orderService.findById(id),
                inventoryClient.checkStock(id),
                paymentClient.getPaymentStatus(id)
            )
            .map(tuple -> new OrderDetail(
                tuple.getT1(), tuple.getT2(), tuple.getT3()
            ));
    }

    @PostMapping("/orders")
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<Order> createOrder(@RequestBody CreateOrderRequest req) {
        // 链式调用,逻辑分散在多个 operator 中
        return inventoryClient.reserve(req.getProductId(), req.getQuantity())
            .flatMap(reserved -> paymentClient.charge(req.getAmount()))
            .flatMap(payment -> orderService.create(req, payment.getTxnId()))
            .onErrorResume(PaymentFailedException.class, e ->
                inventoryClient.release(req.getProductId())
                    .then(Mono.error(e))
            );
    }
}
错误处理对比
java 复制代码
// ========== 虚拟线程:传统 try-catch,直觉友好 ==========
@GetMapping("/orders/{id}")
public ResponseEntity<?> getOrder(@PathVariable Long id) {
    try {
        Order order = orderService.findById(id);
        return ResponseEntity.ok(order);
    } catch (OrderNotFoundException e) {
        return ResponseEntity.status(404).body(new ErrorResponse("订单不存在", e.getMessage()));
    } catch (ServiceUnavailableException e) {
        return ResponseEntity.status(503).body(new ErrorResponse("服务不可用", e.getMessage()));
    }
}

// ========== WebFlux:onErrorResume 链式处理 ==========
@GetMapping("/orders/{id}")
public Mono<ResponseEntity<?>> getOrder(@PathVariable Long id) {
    return orderService.findById(id)
        .<ResponseEntity<?>>map(order -> ResponseEntity.ok(order))
        .onErrorResume(OrderNotFoundException.class, e ->
            Mono.just(ResponseEntity.status(404)
                .body(new ErrorResponse("订单不存在", e.getMessage()))))
        .onErrorResume(ServiceUnavailableException.class, e ->
            Mono.just(ResponseEntity.status(503)
                .body(new ErrorResponse("服务不可用", e.getMessage()))));
}

3.3 数据库访问层对比

java 复制代码
// ========== 虚拟线程 + JPA(零改动,直接使用) ==========
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

    @Query("SELECT o FROM Order o WHERE o.userId = :userId AND o.status = :status")
    List<Order> findByUserIdAndStatus(@Param("userId") Long userId,
                                      @Param("status") OrderStatus status);

    // JPA 的阻塞调用在虚拟线程下自动变为非阻塞
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT o FROM Order o WHERE o.id = :id")
    Order findByIdForUpdate(@Param("id") Long id);
}

// ========== WebFlux + R2DBC(需要响应式驱动) ==========
@Repository
public interface OrderRepository extends ReactiveCrudRepository<Order, Long> {

    @Query("SELECT * FROM orders WHERE user_id = :userId AND status = :status")
    Flux<Order> findByUserIdAndStatus(@Param("userId") Long userId,
                                      @Param("status") String status);
    // 注意:部分 JPA 特性在 R2DBC 中不可用
    // - 没有 @Lock 悲观锁
    // - 没有 Lazy Loading
    // - 没有 JPA EntityManager
    // - 事务管理需要使用 @Transactional(transactionManager = "... ")
}

四、性能基准测试

4.1 测试场景

复制代码
┌─────────────── 压测环境 ───────────────────────────┐
│  服务器:4 Core / 8GB RAM                           │
│  JDK:21.0.2                                        │
│  Spring Boot:3.4.0                                 │
│  压测工具:wrk -t12 -c5000 -d60s                    │
│  场景:查询订单 → 调用库存服务(50ms延迟) → 返回       │
└─────────────────────────────────────────────────────┘

4.2 测试结果

指标 MVC + 平台线程 MVC + 虚拟线程 WebFlux
吞吐量 (req/s) 1,850 42,300 45,100
P50 延迟 (ms) 52 55 53
P99 延迟 (ms) 2,340 180 165
平均线程数 200 5,200 8
CPU 利用率 28% 82% 85%
GC 暂停 (ms) 12 35 18
内存占用 (MB) 256 380 210

4.3 结果分析

复制代码
吞吐量对比(越高越好)                    P99延迟对比(越低越好)
┌──────────────────────────┐         ┌──────────────────────────┐
│ MVC+平台线程  ██▌  1850  │         │ MVC+平台线程  ██████████ │
│ MVC+虚拟线程  ████████42K│         │ 2340ms                   │
│ WebFlux       █████████45K│        │ MVC+虚拟线程  █         │
│                          │         │ 180ms                    │
│ 0     20K    40K    50K  │         │ WebFlux      █          │
│                          │         │ 165ms                    │
└──────────────────────────┘         └──────────────────────────┘

结论:虚拟线程在吞吐量上已接近 WebFlux 水平(~94%),但 P99 延迟略高。考虑到代码复杂度的巨大差异,虚拟线程的性价比极高。


五、选型决策流程图

复制代码
                    ┌─────────────────────────────┐
                    │     你的项目是高并发场景吗?    │
                    └──────────────┬──────────────┘
                                   │
                    ┌──────────────┴──────────────┐
                    │                             │
                   No                            Yes
                    │                             │
                    ▼                             ▼
            ┌──────────────┐          ┌─────────────────────┐
            │ Spring MVC   │          │ QPS > 10K ?         │
            │ + 平台线程    │          └──────────┬──────────┘
            │ (够用了)    │                     │
            └──────────────┘          ┌──────────┴──────────┐
                                      │                     │
                                     No                    Yes
                                      │                     │
                                      ▼                     ▼
                              ┌──────────────┐    ┌──────────────────────┐
                              │ Spring MVC   │    │ 团队有响应式编程经验?  │
                              │ + 虚拟线程    │    └──────────┬───────────┘
                              │ (推荐首选)  │               │
                              └──────────────┘    ┌──────────┴──────────┐
                                                  │                     │
                                                 No                    Yes
                                                  │                     │
                                                  ▼                     ▼
                                          ┌──────────────┐    ┌──────────────┐
                                          │ Spring MVC   │    │ Spring       │
                                          │ + 虚拟线程    │    │ WebFlux      │
                                          │ (依然推荐)  │    │ (可以考虑)  │
                                          └──────────────┘    └──────────────┘

补充决策因素

复制代码
┌──────────────────────────────────────────────────────────────────┐
│                     优先选择虚拟线程的场景                          │
├──────────────────────────────────────────────────────────────────┤
│  ✅ 已有 Spring MVC 项目需要提升并发能力                           │
│  ✅ 团队对响应式编程不熟悉                                         │
│  ✅ 依赖 JPA/Hibernate、MyBatis 等阻塞型 ORM                     │
│  ✅ 需要与大量阻塞式 SDK 集成(Redis 客户端、消息队列等)            │
│  ✅ 代码可读性和可维护性是首要考量                                   │
│  ✅ 调试和排障效率要求高                                           │
├──────────────────────────────────────────────────────────────────┤
│                     依然选择 WebFlux 的场景                        │
├──────────────────────────────────────────────────────────────────┤
│  ✅ SSE / WebSocket 长连接为主的场景                               │
│  ✅ 团队已有成熟的响应式编程经验                                    │
│  ✅ 对极致低延迟有要求(如实时交易系统)                              │
│  ✅ 需要流式处理(backpressure 控制)                               │
│  ✅ 已有大量 WebFlux 基础设施投入                                  │
└──────────────────────────────────────────────────────────────────┘

六、迁移指南:从传统 MVC 到虚拟线程

6.1 最小化改动迁移

java 复制代码
// Step 1: 升级到 Spring Boot 3.2+ 和 JDK 21
// pom.xml
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.0</version>
</parent>

<properties>
    <java.version>21</java.version>
</properties>

// Step 2: 开启虚拟线程
// application.yml
spring:
  threads:
    virtual:
      enabled: true

// Step 3: 完成!你的 Spring MVC 应用已经使用虚拟线程了

6.2 需要注意的 Pinning 问题

java 复制代码
// ⚠️ 虚拟线程的 "Pinning" 问题:
// 如果在 synchronized 块中执行阻塞操作,虚拟线程无法卸载

// ❌ 不好的写法 --- 会 pin 住载体线程
public synchronized Order processOrder(Long id) {
    // 这个 synchronized 会导致虚拟线程 pinning
    return orderService.findById(id);  // 阻塞 I/O
}

// ✅ 好的写法 --- 使用 ReentrantLock 替代 synchronized
private final ReentrantLock lock = new ReentrantLock();

public Order processOrder(Long id) {
    lock.lock();
    try {
        return orderService.findById(id);
    } finally {
        lock.unlock();
    }
}

// ✅ 或者使用 JDK 24+ 的改进(synchronized 不再 pin)
// JVM 参数:-XX:+UnlockExperimentalVMOptions

6.3 自定义虚拟线程 Executor

java 复制代码
@Configuration
public class VirtualThreadConfig {

    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerCustomizer() {
        return handler -> {
            handler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

七、总结

虚拟线程方案 WebFlux 方案
上手成本 几乎为零(加一行配置) 高(学习 Reactor API)
代码改动 无需改动业务代码 需要全面重写
并发性能 接近 WebFlux(~94%) 最高
生态兼容 完全兼容现有生态 仅支持响应式驱动
调试体验 正常的堆栈信息 响应式堆栈难以阅读
未来趋势 Java 主推方向 维持现状,增量改进

一句话建议

对 90% 的高并发 Java 项目而言,Spring MVC + 虚拟线程是 2026 年的最佳选择。 它以近乎零的学习成本换取了接近 WebFlux 的并发性能,同时保留了传统同步编程的所有优势。只有在极端低延迟、流式处理、或已有 WebFlux 深度投入的场景下,才需要考虑 WebFlux。


本文代码基于 Spring Boot 3.4.0 + JDK 21 编写,所有示例均可直接运行。

相关推荐
Flying pigs~~2 小时前
基于Bert的模型迁移文本分类项目
人工智能·深度学习·算法·大模型·nlp·bert
曹牧2 小时前
Tomcat 启动内存的设置
java·tomcat
新加坡内哥谈技术2 小时前
AI代理可能会让自由软件再次变得重要
人工智能·ai编程
七夜zippoe2 小时前
云原生Java应用部署:将Spring Boot应用迁移至Kubernetes完整指南
java·spring boot·docker·云原生·kuberbertes
Fleshy数模2 小时前
从零实现Word2Vec之CBOW模型:理解词向量的核心原理
人工智能·自然语言处理·word2vec
ywf12152 小时前
java进阶1——JVM
java·开发语言·jvm
鱼鳞_2 小时前
Java学习笔记_Day18(数据结构)
java·笔记·学习
风象南2 小时前
学了 100 个 AI 工具,不如把 1 个用到极致
人工智能