Java 响应式编程详解:从 Flux、Mono 到 Spring WebFlux 技术生态

目录

[Java 响应式编程详解:从 Flux、Mono 到 Spring WebFlux 技术生态](#Java 响应式编程详解:从 Flux、Mono 到 Spring WebFlux 技术生态)

[一、为什么 Java 需要响应式编程](#一、为什么 Java 需要响应式编程)

[二、Reactive Streams:整个体系的基础](#二、Reactive Streams:整个体系的基础)

[Backpressure ------ 背压](#Backpressure —— 背压)

[三、Project Reactor:Java Flux 的真正来源](#三、Project Reactor:Java Flux 的真正来源)

[四、Mono:表示 0 或 1 个异步结果](#四、Mono:表示 0 或 1 个异步结果)

[五、Flux:表示 0 到 N 个异步结果](#五、Flux:表示 0 到 N 个异步结果)

[六、Flux 和 List 的本质区别](#六、Flux 和 List 的本质区别)

[七、Flux 最重要的编程方式:Operator](#七、Flux 最重要的编程方式:Operator)

[八、Flux 中最常见的 Operator](#八、Flux 中最常见的 Operator)

map

filter

flatMap

[九、flatMap 和 concatMap 的区别](#九、flatMap 和 concatMap 的区别)

十、zip:并行调用多个服务

十一、错误处理

[十二、Reactive 的一个关键概念:Lazy](#十二、Reactive 的一个关键概念:Lazy)

[十三、Spring WebFlux](#十三、Spring WebFlux)

[十四、WebFlux Controller](#十四、WebFlux Controller)

[十五、WebClient:Reactive HTTP Client](#十五、WebClient:Reactive HTTP Client)

[十六、为什么不应该随便使用 block()](#十六、为什么不应该随便使用 block())

十七、如果必须调用阻塞代码怎么办?

[十八、Scheduler:Reactor 的线程调度体系](#十八、Scheduler:Reactor 的线程调度体系)

[十九、subscribeOn 和 publishOn](#十九、subscribeOn 和 publishOn)

[二十、Reactor Netty](#二十、Reactor Netty)

[二十一、R2DBC:数据库 Reactive 化](#二十一、R2DBC:数据库 Reactive 化)

[二十二、WebFlux 特别适合 AI 流式输出](#二十二、WebFlux 特别适合 AI 流式输出)

[二十三、WebFlux 也非常适合 IoT 和实时数据](#二十三、WebFlux 也非常适合 IoT 和实时数据)

[二十四、Flux 与 CompletableFuture 的区别](#二十四、Flux 与 CompletableFuture 的区别)

[二十五、Flux、Stream、Future、List 的关系](#二十五、Flux、Stream、Future、List 的关系)

[二十六、Java Reactive 技术栈全景](#二十六、Java Reactive 技术栈全景)

[二十七、什么时候应该选择 WebFlux?](#二十七、什么时候应该选择 WebFlux?)

[比较适合 WebFlux](#比较适合 WebFlux)

[不一定适合 WebFlux](#不一定适合 WebFlux)

[二十八、Reactive 最容易出现的架构误区](#二十八、Reactive 最容易出现的架构误区)

[二十九、学习 Flux 最重要的思维转换](#二十九、学习 Flux 最重要的思维转换)

三十、总结

Java 响应式编程详解:从 Flux、Mono 到 Spring WebFlux 技术生态

在 Java 后端开发中,我们经常会看到这样的代码:

复制代码
Flux<User> users = userService.findAll();

Mono<User> user = userService.findById(id);

对于长期使用 Spring MVC、MyBatis、JPA 的开发者来说,第一次接触 FluxMono 往往会有一个疑问:

Flux 到底是什么?为什么 Spring WebFlux、WebClient、R2DBC 都大量使用它?它和传统的 List、Future、CompletableFuture 有什么区别?

实际上,Flux 并不是一种独立的"Flux 编程语言",而是 Project Reactor 提供的核心响应式类型之一

围绕 Flux 展开的,是 Java 生态中的一整套 Reactive Programming(响应式编程)技术体系:

复制代码
Reactive Streams
       │
       ▼
Project Reactor
   │        │
 Mono      Flux
   │        │
   └────┬───┘
        ▼
   Spring WebFlux
        │
   ┌────┼─────────┐
   ▼    ▼         ▼
WebClient R2DBC  Reactor Netty

Spring WebFlux 的核心依赖就是 reactor-core,其 API 通常以 MonoFlux 表达异步结果。Spring 官方也明确将 Reactor 作为 WebFlux 的主要响应式库。(Home)

本文从 Java 工程实践角度,系统介绍 Flux、Mono、Reactive Streams、Project Reactor、Spring WebFlux、WebClient、Reactor Netty、R2DBC,以及响应式编程中最容易踩坑的线程模型和阻塞问题。


一、为什么 Java 需要响应式编程

先看一个最传统的 Spring MVC 接口:

复制代码
@GetMapping("/user/{id}")
public User getUser(@PathVariable Long id) {
    return userService.findById(id);
}

如果 findById() 内部执行数据库查询:

复制代码
User user = jdbcTemplate.queryForObject(...);

当前线程通常需要等待数据库返回结果。

整个过程大致是:

复制代码
HTTP Request
     │
     ▼
Tomcat Thread
     │
     ├── 查询数据库 ────────┐
     │                    │
     │      等待......     │
     │                    │
     ◀────────────────────┘
     │
     ▼
HTTP Response

关键问题是:

等待数据库、Redis、HTTP 服务等 I/O 返回期间,线程可能处于阻塞状态。

传统 Servlet 系统通常通过线程池解决并发:

复制代码
Request 1 → Thread 1
Request 2 → Thread 2
Request 3 → Thread 3
...
Request N → Thread N

这种模型非常成熟,而且对于大量企业系统来说完全够用。

但是当系统进入大量 I/O 等待场景,例如:

  • API Gateway

  • AI 大模型流式接口

  • SSE

  • WebSocket

  • IM 即时通信

  • IoT 长连接

  • 大量微服务调用

  • 高并发 HTTP 聚合服务

线程数量就可能成为重要的资源约束。

响应式编程解决的核心问题之一,就是:

尽量不要让线程因为等待 I/O 而长期阻塞。

Spring WebFlux 正是 Spring 提供的 Reactive Web Stack。官方定义中,WebFlux 是 fully non-blocking 的 Web Framework,同时支持 Reactive Streams Backpressure。(Home)


二、Reactive Streams:整个体系的基础

理解 Flux 之前,需要先理解 Reactive Streams。

Reactive Streams 定义了几个非常核心的接口:

复制代码
Publisher<T>
Subscriber<T>
Subscription
Processor<T, R>

可以把它简化理解成:

复制代码
Publisher
   │
   │ 发布数据
   ▼
Subscriber

但是这里存在一个问题。

假设 Publisher 每秒产生:

复制代码
100000 条数据

Subscriber 每秒只能处理:

复制代码
1000 条数据

怎么办?

如果生产者一直生产,消费者处理不过来,就可能造成:

复制代码
内存不断堆积
      ↓
GC 压力增加
      ↓
延迟增加
      ↓
OOM

因此 Reactive Streams 引入了一个非常重要的机制:

Backpressure ------ 背压

消费者可以告诉生产者:

复制代码
我现在只需要 10 条。

对应概念类似:

复制代码
subscription.request(10);

于是整个关系变成:

复制代码
Publisher
    ▲
    │ request(n)
    │
Subscriber

这就是响应式编程非常核心的思想之一:

数据消费能力反向影响数据生产速度。

Reactor 建立在 Reactive Streams 规范之上,因此其操作符天然围绕非阻塞数据流和需求管理进行设计。(Project Reactor)


三、Project Reactor:Java Flux 的真正来源

Java 生态里真正提供 FluxMono 的项目叫:

Project Reactor

Project Reactor 官方网站

它是 JVM 上非常重要的 Reactive Streams 实现,也是 Spring WebFlux 的核心响应式基础。

截至本文撰写时,Project Reactor 官方文档列出的 Reactor Core 稳定版本已经进入 3.8.x 系列。(Project Reactor)

Reactor 最核心的两个类:

复制代码
Mono<T>
Flux<T>

可以简单记成:

复制代码
Mono = 0 或 1 个结果

Flux = 0 到 N 个结果

Spring 官方也使用这种 cardinality,也就是"结果数量语义",解释两者之间的区别。(Home)


四、Mono:表示 0 或 1 个异步结果

例如查询一个用户:

复制代码
Mono<User> userMono =
        userRepository.findById(1001L);

结果可能是:

复制代码
0 个 User

或者

1 个 User

因此适合使用:

复制代码
Mono<User>

常见场景:

复制代码
Mono<User>
Mono<Order>
Mono<String>
Mono<ResponseEntity<User>>
Mono<Void>

例如:

复制代码
Mono<User> mono = Mono.just(
        new User(1L, "Tom")
);

执行:

复制代码
mono.subscribe(System.out::println);

这里开始出现响应式编程与传统命令式编程非常大的区别。


五、Flux:表示 0 到 N 个异步结果

Flux 可以表示多个数据:

复制代码
Flux<User>

例如:

复制代码
Flux<String> flux =
        Flux.just("Java", "Spring", "Reactor");

或者:

复制代码
Flux<Integer> flux =
        Flux.range(1, 10);

还可以从集合创建:

复制代码
List<User> users = getUsers();

Flux<User> flux =
        Flux.fromIterable(users);

但要注意:

复制代码
List<User>

和:

复制代码
Flux<User>

虽然都能表达"多个 User",但它们的语义完全不同。


六、Flux 和 List 的本质区别

传统 List:

复制代码
List<User> users = userService.findAll();

通常意味着:

复制代码
执行查询
   ↓
等待
   ↓
数据全部返回
   ↓
构造 List
   ↓
返回

而 Flux 更接近:

复制代码
User1 ─────►
User2 ─────────►
User3 ─────────────►
User4 ─────────────────►

数据可以随着时间逐步产生。

所以:

复制代码
Flux<User>

更准确地理解应该是:

一个可以在时间维度上持续产生 User 的异步数据序列。

因此 Flux 特别适合:

复制代码
SSE
WebSocket
消息流
实时日志
AI Token Streaming
数据库 Reactive Cursor
IoT 数据流

例如:

复制代码
Flux.interval(Duration.ofSeconds(1))
    .map(i -> "message-" + i)
    .subscribe(System.out::println);

它可以不断产生:

复制代码
message-0
message-1
message-2
message-3
...

这已经不是普通 List 能表达的语义了。


七、Flux 最重要的编程方式:Operator

Flux 编程的核心不是 for 循环,而是:

Operator Pipeline

例如:

复制代码
Flux.range(1, 10)
    .filter(i -> i % 2 == 0)
    .map(i -> i * 10)
    .subscribe(System.out::println);

逻辑是:

复制代码
1 2 3 4 5 6 7 8 9 10
          │
       filter
          │
          ▼
      2 4 6 8 10
          │
         map
          │
          ▼
     20 40 60 80 100

这种思想和 Java Stream 很像:

复制代码
list.stream()
    .filter(...)
    .map(...)
    .toList();

但两者有一个本质区别:

复制代码
Java Stream
    ↓
同步数据处理

Flux
    ↓
异步 Reactive Stream

Reactor 官方也将 Flux 定义为可组合的异步序列 API,而不仅仅是集合处理工具。(Project Reactor)


八、Flux 中最常见的 Operator

如果开发 Spring WebFlux,下面这些操作符几乎每天都会使用。

map

同步转换:

复制代码
Flux<User> users = ...

Flux<String> names =
    users.map(User::getName);

相当于:

复制代码
User → String

filter

过滤:

复制代码
users.filter(user -> user.getAge() >= 18);

flatMap

这是 Reactor 中非常重要的操作符。

例如:

复制代码
Flux<User> users = ...

Flux<Order> orders =
    users.flatMap(user ->
        orderService.findOrders(user.getId())
    );

假设:

复制代码
findOrders()

返回:

复制代码
Flux<Order>

如果使用 map

复制代码
users.map(user ->
    orderService.findOrders(user.getId())
);

结果会变成:

复制代码
Flux<Flux<Order>>

flatMap 会把内部 Publisher 展平:

复制代码
Flux<User>
    │
    ▼
flatMap
    │
    ▼
Flux<Order>

所以可以粗略记忆:

复制代码
普通对象转换 → map

异步 Publisher 转换 → flatMap

九、flatMap 和 concatMap 的区别

这是实际开发中很重要的问题。

例如:

复制代码
Flux.just(1, 2, 3)
    .flatMap(this::request)

request() 是异步调用。

那么执行可能是:

复制代码
1 ────────► result1

2 ──► result2

3 ─────► result3

最终返回顺序可能变成:

复制代码
result2
result3
result1

因为:

复制代码
flatMap()

允许并发处理。

如果业务要求严格保持顺序:

复制代码
Flux.just(1, 2, 3)
    .concatMap(this::request);

可以理解为:

复制代码
flatMap
    → 并发
    → 不保证完成顺序

concatMap
    → 串行
    → 保证顺序

这在批量写数据库、消息处理、工作流执行中尤其重要。


十、zip:并行调用多个服务

微服务场景中经常遇到:

复制代码
获取用户信息
获取订单信息
获取积分信息

传统写法可能是:

复制代码
User user = userService.getUser(id);

List<Order> orders =
    orderService.getOrders(id);

Points points =
    pointService.getPoints(id);

如果三个请求分别需要:

复制代码
100 ms
200 ms
150 ms

串行总耗时大约:

复制代码
450 ms

Reactive 可以:

复制代码
Mono<User> userMono =
    userService.getUser(id);

Mono<List<Order>> orderMono =
    orderService.getOrders(id);

Mono<Points> pointMono =
    pointService.getPoints(id);

return Mono.zip(
        userMono,
        orderMono,
        pointMono
    )
    .map(tuple -> new UserDashboard(
        tuple.getT1(),
        tuple.getT2(),
        tuple.getT3()
    ));

逻辑变成:

复制代码
              ┌─ User Service
              │
Request ──────┼─ Order Service
              │
              └─ Point Service
                    │
                    ▼
                   zip
                    │
                    ▼
               Dashboard

理论上的整体 I/O 等待时间更接近最慢的那个请求,而不是三次请求耗时简单相加。


十一、错误处理

传统 Java:

复制代码
try {

    return service.call();

} catch (Exception e) {

    log.error("error", e);

    return fallback();
}

Reactor 更强调错误也是数据流中的一种 Signal:

复制代码
onNext
onComplete
onError

例如:

复制代码
service.call()
    .onErrorReturn(defaultValue);

或者:

复制代码
service.call()
    .onErrorResume(ex -> {

        log.error("call failed", ex);

        return backupService.call();
    });

还可以重试:

复制代码
service.call()
    .retry(3);

或者:

复制代码
service.call()
    .retryWhen(
        Retry.backoff(
            3,
            Duration.ofSeconds(1)
        )
    );

这种模式非常适合:

复制代码
微服务调用
第三方 API
Redis
MQ
AI API

十二、Reactive 的一个关键概念:Lazy

下面这段代码:

复制代码
Flux<Integer> flux =
    Flux.range(1, 10)
        .map(i -> {
            System.out.println(i);
            return i * 10;
        });

此时通常:

复制代码
什么都不会执行。

为什么?

因为 Reactor 默认是:

复制代码
Lazy

只有发生订阅:

复制代码
flux.subscribe();

数据流才开始运行。

可以理解为:

复制代码
定义 Pipeline

Flux
 ↓
map
 ↓
filter
 ↓
flatMap
 ↓
zip

只是描述:

数据到来以后应该怎么处理。

真正执行通常从:

复制代码
subscribe()

开始。

因此 Reactor 经常有一句非常重要的概念:

复制代码
Nothing happens until you subscribe.

十三、Spring WebFlux

Flux 真正在 Java 企业开发中大规模出现,主要原因就是:

Spring WebFlux

Spring WebFlux 官方文档

Spring WebFlux 从 Spring Framework 5.0 开始提供,与 Spring MVC 并列存在。当前 Spring 官方文档仍同时维护 Servlet Stack 和 Reactive Stack。(Home)

传统 Spring MVC:

复制代码
Spring MVC
    │
 Servlet API
    │
 Tomcat

典型 WebFlux:

复制代码
Spring WebFlux
      │
 Project Reactor
      │
 Reactor Netty

WebFlux 同样也可以运行在部分 Servlet 容器之上。Spring 的 Reactive Core 为 Reactor Netty、Tomcat、Jetty 等服务器提供了相应适配。(Home)


十四、WebFlux Controller

一个典型 WebFlux Controller:

复制代码
@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    @GetMapping("/{id}")
    public Mono<User> getUser(
            @PathVariable Long id) {

        return userService.findById(id);
    }

    @GetMapping
    public Flux<User> getUsers() {

        return userService.findAll();
    }
}

从 API 设计上就能看到语义:

复制代码
GET /users/1

Mono<User>

0..1

而:

复制代码
GET /users

Flux<User>

0..N

这也是 Mono 和 Flux 把"结果基数"直接表达进 Java 类型系统的价值。


十五、WebClient:Reactive HTTP Client

WebFlux 生态中另一个非常重要的组件是:

复制代码
WebClient

例如调用用户服务:

复制代码
WebClient webClient =
    WebClient.builder()
        .baseUrl("http://user-service")
        .build();

调用:

复制代码
Mono<User> user =
    webClient.get()
        .uri("/users/{id}", id)
        .retrieve()
        .bodyToMono(User.class);

查询列表:

复制代码
Flux<User> users =
    webClient.get()
        .uri("/users")
        .retrieve()
        .bodyToFlux(User.class);

于是整个调用链可以保持 Reactive:

复制代码
HTTP Request
      │
      ▼
Controller
      │
      ▼
Service
      │
      ▼
WebClient
      │
      ▼
Remote Service
      │
      ▼
Mono / Flux

而不是中间突然:

复制代码
.block();

十六、为什么不应该随便使用 block()

这是 WebFlux 项目里非常典型的坑。

例如:

复制代码
Mono<User> mono =
    userService.findById(id);

User user = mono.block();

虽然代码看起来舒服了:

复制代码
Reactive
   ↓
block()
   ↓
同步代码

但这实际上可能破坏 Reactive Pipeline 的非阻塞特性。

尤其不要形成这种代码:

复制代码
return webClient.get()
    .uri("/users/1")
    .retrieve()
    .bodyToMono(User.class)
    .map(user -> {

        Order order =
            orderService.getOrder()
                        .block();

        return build(user, order);
    });

这种代码从表面上看用了:

复制代码
WebFlux
WebClient
Mono

实际上内部仍然:

复制代码
Blocking

因此响应式系统的一个重要原则是:

Reactive 要尽可能 end-to-end。


十七、如果必须调用阻塞代码怎么办?

现实中的 Java 系统不可能全部 Reactive。

例如你可能还在使用:

复制代码
JDBC
MyBatis
旧 SDK
本地文件 API
Legacy Service

这些调用可能都是 Blocking 的。

这时可以考虑:

复制代码
Mono.fromCallable(() -> {

    return legacyService.query();

})
.subscribeOn(
    Schedulers.boundedElastic()
);

逻辑相当于:

复制代码
EventLoop
   │
   │ 遇到 Blocking Task
   ▼
boundedElastic
   │
   ▼
执行 Blocking IO

这样可以避免直接阻塞关键 EventLoop 线程。

但这是一种隔离阻塞调用的兼容策略,并不意味着阻塞 API 自动变成了真正的非阻塞 I/O。


十八、Scheduler:Reactor 的线程调度体系

Reactor 中几个常见 Scheduler:

复制代码
Schedulers.parallel()

Schedulers.boundedElastic()

Schedulers.single()

Schedulers.immediate()

其中开发中最需要理解的是:

复制代码
parallel

和:

复制代码
boundedElastic

可以粗略理解:

复制代码
parallel
   │
   └─ CPU 密集型任务

boundedElastic
   │
   └─ Blocking / 较慢 I/O

例如:

复制代码
Flux.range(1, 100)
    .parallel()
    .runOn(Schedulers.parallel())
    .map(this::calculate)
    .sequential();

而 Legacy Blocking API:

复制代码
Mono.fromCallable(() ->
        jdbcTemplate.queryForObject(...)
    )
    .subscribeOn(
        Schedulers.boundedElastic()
    );

十九、subscribeOn 和 publishOn

这是 Reactor 学习中的另一个难点。

例如:

复制代码
Flux.range(1, 10)

    .map(this::step1)

    .publishOn(
        Schedulers.parallel()
    )

    .map(this::step2);

可以粗略理解:

复制代码
step1
  │
  │
publishOn
  │
  ▼
切换后续执行 Scheduler
  │
step2

而:

复制代码
subscribeOn(...)

主要影响:

复制代码
Subscription / Source

所在的执行上下文。

简单记忆:

复制代码
subscribeOn
    → 更偏向决定"源从哪里开始执行"

publishOn
    → 更偏向决定"后面的操作在哪里执行"

实际项目中,不建议为了"看起来异步"而到处切 Scheduler。线程切换本身也存在成本。


二十、Reactor Netty

WebFlux 默认技术栈中经常出现:

Reactor Netty

它建立在 Netty 之上,并提供 Reactive 风格的:

复制代码
HTTP Server
HTTP Client
TCP
UDP
WebSocket

Reactor 官方将其定位为支持 Backpressure 的非阻塞网络引擎。(Project Reactor)

典型结构:

复制代码
Spring WebFlux
      │
      ▼
Project Reactor
      │
      ▼
Reactor Netty
      │
      ▼
Netty EventLoop
      │
      ▼
Linux epoll / socket

这也是为什么 WebFlux 非常强调:

复制代码
不要阻塞 EventLoop

因为少量 EventLoop Thread 可能同时承担大量 Connection。

如果其中某个线程被:

复制代码
Thread.sleep(10000);

或者:

复制代码
JDBC.query(...)

长期占住,就可能影响同一个 EventLoop 上的其他连接。


二十一、R2DBC:数据库 Reactive 化

如果 HTTP 层使用 WebFlux:

复制代码
WebFlux
   ↓
Reactive

但是数据库仍然:

复制代码
JDBC
   ↓
Blocking

那么整个链路实际上没有做到真正的 End-to-End Non-blocking。

因此 Java Reactive 生态出现了:

复制代码
R2DBC

即:

复制代码
Reactive Relational Database Connectivity

对应关系可以理解成:

复制代码
传统:

Spring MVC
   ↓
JDBC
   ↓
MySQL

Reactive:

复制代码
Spring WebFlux
      ↓
     R2DBC
      ↓
    MySQL

Repository 可以直接返回:

复制代码
Mono<User>

或者:

复制代码
Flux<User>

最终形成:

复制代码
HTTP
 ↓
WebFlux
 ↓
Service
 ↓
R2DBC
 ↓
Database

整个 Pipeline 都使用 Reactive 类型。


二十二、WebFlux 特别适合 AI 流式输出

现在 Flux 一个非常典型的应用就是:

LLM Streaming API。

例如模型不断返回 Token:

复制代码
你
好
,
这
是
一
个
AI
助
手
...

服务端天然可以表示成:

复制代码
Flux<String>

例如:

复制代码
@GetMapping(
    value = "/chat",
    produces = MediaType.TEXT_EVENT_STREAM_VALUE
)
public Flux<String> chat(String prompt) {

    return llmService.stream(prompt);
}

数据流:

复制代码
LLM
 │
 ├── token1
 ├── token2
 ├── token3
 ├── token4
 │
 ▼
Flux<String>
 │
 ▼
Spring WebFlux
 │
 ▼
SSE
 │
 ▼
Browser

这比:

复制代码
String result = waitUntilLLMFinished();

更符合 LLM Streaming 的业务模型。

因此现在很多:

复制代码
AI Gateway
Agent
SSE
Streaming Chat
模型代理服务

都非常适合使用 Reactive Pipeline。


二十三、WebFlux 也非常适合 IoT 和实时数据

例如设备持续上报:

复制代码
Temperature
Humidity
CO₂
Light

可以抽象为:

复制代码
Flux<DeviceTelemetry>

然后:

复制代码
telemetryFlux
    .filter(this::isValid)
    .map(this::normalize)
    .flatMap(this::save)
    .flatMap(this::checkAlarm)
    .subscribe();

数据流非常清晰:

复制代码
MQTT
 │
 ▼
Flux<DeviceTelemetry>
 │
 ▼
filter
 │
 ▼
normalize
 │
 ▼
save
 │
 ▼
alarm

所以 Reactive Programming 很适合描述:

持续发生的数据,而不仅仅是一次请求对应一次结果。


二十四、Flux 与 CompletableFuture 的区别

Java 开发者通常还会问:

复制代码
Flux / Mono

和

CompletableFuture

有什么区别?

CompletableFuture<T> 更适合表达:

复制代码
未来某个时间
产生一个结果

所以它和:

复制代码
Mono<T>

有一定相似性。

例如:

复制代码
CompletableFuture<User>

与:

复制代码
Mono<User>

都可以表示异步单结果。

但是 Flux 能表达:

复制代码
0..N

例如:

复制代码
T1 ─────►
T2 ─────────►
T3 ─────────────►
T4 ─────────────────►

这是单个 CompletableFuture<T> 不擅长表达的。

另外 Reactor 还提供:

复制代码
Operator
Backpressure
Error Pipeline
Scheduler
Retry
Timeout
Buffer
Window
Merge
Zip
Combine

因此 Reactor 更像:

完整的异步数据流编程模型。

而 CompletableFuture 更偏向:

异步任务结果编排。


二十五、Flux、Stream、Future、List 的关系

可以用一张表快速理解:

类型 数据数量 异步 流式 Backpressure
List<T> N
Stream<T> N 通常否
Future<T> 1
CompletableFuture<T> 1
Mono<T> 0..1
Flux<T> 0..N

所以 Flux 并不是简单的:

复制代码
异步 List

更准确地说:

复制代码
Flux<T>

=

异步
+
数据流
+
Reactive Streams
+
Backpressure
+
Operator Pipeline

二十六、Java Reactive 技术栈全景

如果把 Java 中与 Flux 相关的技术放在一起,大致可以得到:

复制代码
                Reactive Streams
                       │
                       ▼
                Project Reactor
                 │           │
                 ▼           ▼
               Mono         Flux
                 │           │
                 └─────┬─────┘
                       │
        ┌──────────────┼───────────────┐
        │              │               │
        ▼              ▼               ▼
 Spring WebFlux     WebClient        R2DBC
        │              │               │
        ▼              ▼               ▼
 Reactor Netty     HTTP Client      Database
        │
        ▼
      Netty

旁边还存在其他 Reactive 技术:

复制代码
RxJava
Mutiny
Kotlin Coroutines
RSocket
Reactive Messaging

WebFlux 本身以 Reactor 为核心,同时可以通过 Reactive Streams 和适配机制与其他响应式库协作;Spring 当前文档列出的内置适配还包括 RxJava 3、Kotlin Coroutines 和 SmallRye Mutiny。(Home)


二十七、什么时候应该选择 WebFlux?

WebFlux 并不是 Spring MVC 的"升级版"。

它们解决的问题不同。

比较适合 WebFlux

如果系统主要是:

复制代码
大量并发连接
+
大量 I/O
+
大量等待

例如:

复制代码
API Gateway
AI Streaming Gateway
SSE
WebSocket
IM
IoT
微服务聚合接口
实时推送
大量外部 HTTP API 调用

WebFlux 很有价值。


不一定适合 WebFlux

如果项目主要是:

复制代码
Controller
   ↓
Service
   ↓
MyBatis
   ↓
MySQL

并且:

复制代码
并发量普通
数据库访问大量 Blocking
团队对 Reactor 不熟悉
业务主要是普通 CRUD

那么 Spring MVC 往往更加简单。

不要为了:

复制代码
"技术先进"

而强行 WebFlux。

因为 Reactive Programming 的学习成本明显高于传统命令式编程。


二十八、Reactive 最容易出现的架构误区

一种很常见的系统:

复制代码
WebFlux
  │
  ▼
Controller
  │
  ▼
Service
  │
  ▼
MyBatis
  │
  ▼
MySQL

然后开发人员认为:

"用了 WebFlux,所以整个系统都是 Non-blocking。"

其实并不是。

MyBatis/JDBC 本身仍属于传统阻塞式访问模式。

如果直接在 EventLoop 上调用:

复制代码
mapper.selectList(...)

就可能造成:

复制代码
EventLoop
    │
    ▼
Blocking JDBC
    │
    ▼
线程被占住

更合理的方案通常有两个:

复制代码
方案一:

WebFlux
 ↓
R2DBC
 ↓
Database

或者对于无法改造的 Legacy 系统:

复制代码
WebFlux
 ↓
boundedElastic
 ↓
Blocking JDBC

后者只是把阻塞操作隔离到适合执行阻塞任务的线程资源中,并没有让 JDBC 本身变成非阻塞。


二十九、学习 Flux 最重要的思维转换

很多 Java 开发者学习 Reactor 最大的问题不是 API,而是思维方式。

传统命令式:

复制代码
User user = getUser();

Order order = getOrder(user);

Result result = build(user, order);

return result;

思维是:

复制代码
先执行 A
得到结果
再执行 B
得到结果
再执行 C

Reactive:

复制代码
return getUser()
    .flatMap(user ->
        getOrder(user)
            .map(order ->
                build(user, order)
            )
    );

思维变成:

复制代码
定义:

当 User 到来的时候
    做什么

当 Order 到来的时候
    做什么

发生 Error 的时候
    做什么

完成的时候
    做什么

所以 Reactive Programming 的本质并不是:

复制代码
Flux API 很多

而是:

从"获取数据以后执行代码",转变成"描述数据到来以后如何流动"。


三十、总结

如果只用一句话解释 Flux:

Flux 是 Project Reactor 提供的、基于 Reactive Streams 的 0..N 异步数据序列抽象。

而 Mono:

Mono 是 0..1 异步结果抽象。

它们共同构成了 Spring Reactive 生态最核心的编程模型:

复制代码
Reactive Streams
       ↓
Project Reactor
       ↓
Flux / Mono
       ↓
Spring WebFlux
       ↓
WebClient / Reactor Netty / R2DBC

真正理解 Flux,需要掌握的不只是:

复制代码
map()
filter()
flatMap()
zip()

更重要的是理解:

复制代码
Non-blocking

Backpressure

EventLoop

Scheduler

Lazy Evaluation

Publisher / Subscriber

End-to-End Reactive

在传统 CRUD 系统里,Spring MVC 依然是非常成熟、高效而且容易维护的方案。

但当业务进入:

复制代码
AI Streaming
SSE
WebSocket
IM
IoT
API Gateway
高并发微服务聚合

这种以大量连接、异步 I/O 和持续数据流为主要特征的场景时,Flux、Project Reactor 和 Spring WebFlux 的价值就会非常明显。

因此,对 Java 后端开发者来说,学习 Flux 的重点并不是简单记住几个 Reactor API,而是理解 Java 服务端正在提供的另一种并发模型:

复制代码
传统模型:

一个请求
   ↓
占用一个线程
   ↓
等待 I/O
   ↓
返回结果

逐渐扩展到:

复制代码
Reactive 模型:

少量 EventLoop
      │
      ├──── Request A ─── I/O ──┐
      ├──── Request B ─── I/O ──┤
      ├──── Request C ─── I/O ──┤
      └──── Request D ─── I/O ──┤
                                │
                数据 Ready ◀────┘
                     │
                     ▼
               Reactive Pipeline

这才是理解 Flux 编程以及 Java Reactive 技术生态的关键。

相关推荐
一只旭宝1 小时前
预约系统版本2(pyhton+flask可视化版本)
服务器·数据库·c++·笔记·python·flask
__zRainy__1 小时前
Node系列 · ORM:mysql 驱动程序
数据库·后端·mysql·node.js·orm
程序员夏洛1 小时前
MySQL 中如果发生死锁应该如何解决?
数据库·mysql
肠畔码农1 小时前
Redis 深度内核解析与高性能运维调优指南
运维·数据库·redis
钱六两1 小时前
SpringAI集成RAG实操(postgresql+pgvector)中(集成到项目中)
数据库·postgresql
万维易源1 小时前
油价行情数据接口整理:国际原油与国内成品油
大数据·数据库·原油·油价
TDengine (老段)2 小时前
TDengine 如何支撑金隅集团水泥业务的能源精细化管控
大数据·数据库·物联网·能源·时序数据库·tdengine·涛思数据
破土士V2 小时前
【MySQL基础知识】数据库约束&联合查询&索引
数据库·mysql·索引·联合查询·数据库约束