目录
[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)
[九、flatMap 和 concatMap 的区别](#九、flatMap 和 concatMap 的区别)
[十二、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 的开发者来说,第一次接触 Flux 和 Mono 往往会有一个疑问:
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 通常以 Mono 和 Flux 表达异步结果。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 生态里真正提供 Flux 和 Mono 的项目叫:
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 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 技术生态的关键。