Project Reactor 是Java响应式编程库 ,提供Mono 和Flux 核心类型,支持非阻塞、背压及异步数据流处理 。它是Spring WebFlux 的基础,适用于构建高并发 、低延迟的微服务与事件驱动应用,遵循Reactive Streams规范。
1. 响应式编程入门:从阻塞困境到数据流之美 2. 深入 Project Reactor:从原理到工程实践的全面指南 3. Flux 与 Mono:Project Reactor 核心响应式类型深度解析 4. Mono:Project Reactor 中最精巧的响应式原语 5. 创建 Flux/Mono 并订阅:Project Reactor 响应式编程的第一步 6. 程序化创建响应式序列:Flux.generate、Flux.create 与 Flux.push 深度解析 7. 线程调度与 Schedulers:Project Reactor 并发模型的核心引擎 8. 响应式流中的错误处理:Project Reactor 异常治理全体系 9. Sinks API:Project Reactor 中程序化发射数据的现代方案
引言
在 Project Reactor 的架构体系中,Flux 与 Mono 是两大核心响应式类型(Reactive Types),它们共同构成了 Reactor 响应式编程模型的基石。作为 Reactive Streams 规范中 Publisher 接口的具体实现,二者承载了从数据源创建、操作符变换、调度器切换到背压控制的全生命周期管理。 理解 Flux 与 Mono 的语义、行为与协作方式,是掌握 Reactor 乃至整个 Spring WebFlux 技术栈的第一步。
一、Reactive Streams 规范回顾
在深入 Flux 与 Mono 之前,有必要回顾 Reactive Streams 规范定义的四个核心接口:
java
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}
public interface Subscriber<T> {
void onSubscribe(Subscription s);
void onNext(T t);
void onError(Throwable t);
void onComplete();
}
public interface Subscription {
void request(long n);
void cancel();
}
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}
信号协议(Signal Protocol):
文本
onSubscribe → onNext × (0..N) → [onComplete | onError]
- onSubscribe:订阅建立时首先触发,传递 Subscription 对象
- onNext:推送数据元素,可触发 0 到 N 次
- onComplete:正常终止信号,此后不再有任何信号
- onError:错误终止信号,此后不再有任何信号
- onComplete 与 onError 互斥,且最多出现一次
Flux 与 Mono 正是对这一协议的两种具体语义化实现。
二、Flux:0 到 N 个元素的异步序列
2.1 定义与语义
FluxT 是一个 Reactive Streams Publisher,可以发射 0 到 N 个元素,然后正常完成(onComplete)或以错误终止(onError)。
文本
Flux.just("A", "B", "C") 的 Marble 图:
──A──B──C──|>
onComplete
Flux.error(new RuntimeException()) 的 Marble 图:
──X>
onError
Flux 适用于表示:
- 集合数据的响应式遍历(如数据库查询结果集)
- 事件流(如 WebSocket 消息、SSE 推送)
- 定时序列(如 Flux.interval())
- 无限流(如传感器数据流、日志流)
2.2 创建 Flux 的方式
Reactor 提供了丰富的静态工厂方法: (1)从已知值创建
java
// 固定元素
Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);
// 空 Flux
Flux<String> empty = Flux.empty();
// 错误 Flux
Flux<Object> error = Flux.error(new RuntimeException("boom"));
(2)从集合/数组创建
java
List<String> list = List.of("alpha", "beta", "gamma");
Flux<String> fromList = Flux.fromIterable(list);
String[] arr = {"x", "y", "z"};
Flux<String> fromArray = Flux.fromArray(arr);
// 从 Java Stream 创建(注意:只能消费一次)
Flux<String> fromStream = Flux.fromStream(list.stream());
(3)程序化生成
java
// range: 生成整数区间
Flux<Integer> range = Flux.range(1, 100);
// interval: 定时发射(无限流)
Flux<Long> ticks = Flux.interval(Duration.ofMillis(500));
// generate: 同步、逐个生成(有状态)
Flux<Integer> generated = Flux.generate(
() -> 0, // 初始状态
(state, sink) -> {
sink.next(state); // 发射当前值
if (state == 10) sink.complete(); // 终止条件
return state + 1; // 返回新状态
}
);
// create: 异步、多线程生成(桥接回调 API)
Flux<String> created = Flux.create(sink -> {
messageListener.onMessage(msg -> sink.next(msg.getBody()));
messageListener.onError(sink::error);
messageListener.onComplete(sink::complete);
sink.onDispose(() -> messageListener.close());
});
// push: 异步、单线程生成
Flux<String> pushed = Flux.push(sink -> {
singleThreadedEmitter.register(sink::next);
});
(4)惰性创建
java
// defer: 每次 subscribe 时才决定创建哪个 Publisher
Flux<Long> deferred = Flux.defer(() ->
Flux.just(System.currentTimeMillis())
);
2.3 Flux 的三种终止形态
| 形态 | 描述 | 示例 |
|---|---|---|
| 有限正常完成 | 发射 N 个元素后发出 onComplete | Flux.just(1,2,3) |
| 错误终止 | 发射 0~N 个元素后发出 onError | Flux.error(...) |
| 无限流 | 永不发出终止信号 | Flux.interval(...) |
⚠️ 无限流必须配合 take(n)、timeout() 或手动 cancel() 使用,否则永远不会结束。
三、Mono:0 或 1 个元素的异步结果
3.1 定义与语义
MonoT 是一个 Reactive Streams Publisher,最多发射 0 或 1 个元素,然后正常完成或以错误终止。
文本
Mono.just("Hello") 的 Marble 图:
──Hello──|>
onComplete
Mono.empty() 的 Marble 图:
──|>
onComplete(无元素)
Mono 的语义等价于:
- Optional + CompletableFuture 的响应式增强版
- 表示一个异步的"可能有值、可能为空、可能失败"的计算结果
适用场景:
- 数据库 findById 查询
- HTTP 请求获取单个资源
- 写操作(save/delete)的结果确认
- 任何"最多返回一个值"的异步操作
3.2 创建 Mono 的方式
java
// 有值
Mono<String> just = Mono.just("Hello Reactor");
// 空值
Mono<Void> empty = Mono.empty();
// 错误
Mono<Object> error = Mono.error(new IllegalArgumentException("bad"));
// 惰性
Mono<Long> deferred = Mono.defer(() -> Mono.just(System.currentTimeMillis()));
// 从 Callable 桥接阻塞调用
Mono<String> fromCallable = Mono.fromCallable(() -> blockingService.fetch());
// 从 CompletableFuture 桥接
Mono<User> fromFuture = Mono.fromFuture(asyncService.findUser(1L));
// 从 Publisher 桥接
Mono<User> fromPublisher = Mono.from(somePublisher);
3.3 Mono 特有的操作
java
Mono<User> userMono = userRepository.findById(1L);
// 如果为空则使用默认值
User user = userMono.block(); // ⚠️ 仅在测试/启动时允许
// 如果为空则切换到另一个 Mono
Mono<User> withFallback = userMono.switchIfEmpty(defaultUserMono);
// 将 Mono<T> 转为 Mono<Void>(忽略值,只关心完成信号)
Mono<Void> ignored = userMono.then();
// 在值到达后执行副作用,但不改变流
Mono<User> withLog = userMono.doOnNext(u -> log.info("Got: {}", u));
// 延迟订阅
Mono<User> delayed = Mono.delay(Duration.ofSeconds(3))
.then(userMono);
四、Flux ↔ Mono 互转
两种类型之间可以灵活转换:
java
// Flux → Mono
Mono<Integer> first = Flux.just(1, 2, 3).next(); // 取第一个
Mono<Integer> last = Flux.just(1, 2, 3).last(); // 取最后一个
Mono<Integer> single = Flux.just(42).single(); // 确保恰好一个,否则报错
Mono<List<Integer>> list = Flux.just(1,2,3).collectList(); // 聚合为 List
Mono<Integer> sum = Flux.just(1,2,3).reduce(0, Integer::sum); // 聚合为单值
// Mono → Flux
Flux<Integer> flux = Mono.just(42).flux(); // 0或1个元素的 Flux
// Mono → 多元素 Flux(flatMapMany)
Flux<Order> orders = userMono.flatMapMany(user ->
orderRepository.findByUserId(user.getId())
);
五、Subscribe:一切执行的起点
5.1 惰性求值原则
Reactor 中最核心的原则之一:
在调用 subscribe() 之前,操作符链仅仅是声明式的"蓝图",不会有任何数据流动或副作用发生。
java
// ❗ 这行代码什么都不会发生
Flux<String> lazy = Flux.just("A", "B", "C")
.map(s -> {
System.out.println("Processing: " + s); // 不会打印
return s.toLowerCase();
});
// ✅ 必须 subscribe 才触发执行
lazy.subscribe(System.out::println);
5.2 subscribe 的重载形式
java
// 1. 最简形式:只消费数据
flux.subscribe(data -> System.out.println(data));
// 2. 数据 + 错误处理
flux.subscribe(
data -> System.out.println(data),
error -> log.error("Stream error", error)
);
// 3. 数据 + 错误 + 完成
flux.subscribe(
data -> System.out.println(data),
error -> log.error("Stream error", error),
() -> System.out.println("Stream completed")
);
// 4. 数据 + 错误 + 完成 + 订阅回调
flux.subscribe(
data -> System.out.println(data),
error -> log.error("Stream error", error),
() -> System.out.println("Done"),
subscription -> subscription.request(10) // 背压:只请求10个
);
5.3 内部执行流程
当 subscribe() 被调用时,内部发生以下流程:
文本
Subscriber.subscribe(Publisher)
→ Publisher 内部创建 Operator 链
→ 从最下游操作符向上游逐层调用 subscribe()
→ 到达源头 Publisher
→ 源头调用 Subscriber.onSubscribe(Subscription)
→ Subscriber 通过 Subscription.request(n) 请求数据
→ 数据沿操作符链从上游向下游流动(onNext)
→ 最终到达终端 Subscriber
这是一个 "订阅信号向上游传播,数据信号向下游流动" 的双向过程。
六、操作符:装配线上的工作站
6.1 操作符的本质
每个操作符接收一个上游 Publisher,返回一个新的 Publisher 实例,并在其中包装了特定的处理逻辑。
java
Flux.just(1, 2, 3, 4, 5) // Publisher A
.filter(i -> i % 2 == 0) // 返回新的 Publisher B(包装了 filter 逻辑)
.map(i -> i * 10) // 返回新的 Publisher C(包装了 map 逻辑)
.subscribe(); // 订阅 C → 触发 B → 触发 A
⚠️ 常见误解:操作符不会修改原始 Publisher,而是创建新的包装实例。如果你"忘记"使用某个操作符的返回值,那个操作符就不会生效。
6.2 核心操作符分类
| 类别 | 操作符 | 说明 |
|---|---|---|
| 转换 | map, flatMap, concatMap, flatMapSequential | 元素变换 |
| 过滤 | filter, distinct, skip, take, takeUntil | 筛选元素 |
| 组合 | merge, concat, zip, combineLatest | 多流合一 |
| 聚合 | reduce, scan, collectList, count | 多值→单值 |
| 窗口 | buffer, window, bufferTimeout, windowTimeout | 分批处理 |
| 错误 | onErrorResume, onErrorReturn, retry, retryWhen | 异常恢复 |
| 调度 | subscribeOn, publishOn | 线程切换 |
| 调试 | log, doOnNext, doOnError, doOnComplete | 生命周期观察 |
6.3 map vs flatMap:最核心的区别
java
// map: 同步 1:1 转换
Flux<String> upper = Flux.just("a", "b", "c")
.map(String::toUpperCase); // A, B, C
// flatMap: 异步 1:N 转换(每个元素映射为一个 Publisher)
Flux<Order> orders = Flux.just(userId1, userId2, userId3)
.flatMap(id -> orderService.findByUserId(id)); // 并发执行,不保序
// concatMap: 异步 1:N 转换,保序
Flux<Order> orderedResults = Flux.just(userId1, userId2, userId3)
.concatMap(id -> orderService.findByUserId(id)); // 顺序执行
七、背压:Flux 独有的挑战
7.1 为什么 Mono 不需要担心背压?
Mono 最多只有一个元素,不存在"生产速度超过消费速度"的问题。而 Flux 可能发射大量甚至无限个元素,背压是 Flux 必须面对的核心问题。
7.2 背压策略
java
Flux<Long> fastProducer = Flux.interval(Duration.ofMillis(1));
// 策略1:缓冲(默认无界,可设上限)
fastProducer.onBackpressureBuffer(1024);
// 策略2:丢弃
fastProducer.onBackpressureDrop();
// 策略3:只保留最新
fastProducer.onBackpressureLatest();
// 策略4:溢出报错
fastProducer.onBackpressureError();
7.3 消费者控制速率
java
Flux.range(1, 1_000_000)
.subscribe(new BaseSubscriber<Integer>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(10); // 初始只请求 10 个
}
@Override
protected void hookOnNext(Integer value) {
slowProcess(value);
request(1); // 每处理完一个,再要一个
}
});
7.4 limitRate:操作符级预取控制
java
Flux.range(1, 1000)
.limitRate(10) // 每次最多向上游请求 10 个元素
.subscribe();
八、Hot vs Cold:两种 Publisher 模式
| 特性 | Cold Publisher | Hot Publisher |
|---|---|---|
| 数据起点 | 每个订阅者从头开始 | 从订阅时刻开始(可能错过之前的数据) |
| 典型代表 | Flux.just(), HTTP 调用 | Flux.create() 的实时事件源 |
| 类比 | 点播视频(从头播放) | 电视直播(进来看到什么算什么) |
| 复用 | 每次订阅独立执行 | 所有订阅者共享同一数据源 |
java
// Cold: 每次 subscribe 都重新执行
Flux<Long> cold = Flux.just(System.currentTimeMillis());
// 变为 Hot(多播)
ConnectableFlux<Long> hot = Flux.interval(Duration.ofSeconds(1)).publish();
hot.connect(); // 开始发射
hot.subscribe(v -> System.out.println("S1: " + v));
Thread.sleep(3000);
hot.subscribe(v -> System.out.println("S2: " + v)); // S2 错过了前 3 个
九、与 Spring WebFlux 的集成
在 Spring WebFlux 中,Controller 方法返回的 Mono / Flux 会由框架自动订阅:
java
@RestController
public class UserController {
// Mono: 返回单个对象或 404
@GetMapping("/users/{id}")
public Mono<ResponseEntity<User>> getUser(@PathVariable Long id) {
return userRepository.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
// Flux: 返回列表
@GetMapping("/users")
public Flux<User> listUsers() {
return userRepository.findAll();
}
// Flux + SSE: 服务端推送
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return eventService.listen()
.map(e -> ServerSentEvent.<String>builder()
.data(e.getMessage())
.build());
}
}
十、测试:StepVerifier
Reactor 提供了 reactor-test 模块中的 StepVerifier 用于精确验证流的行为:
java
@Test
void testFluxBehavior() {
StepVerifier.create(
Flux.just(1, 2, 3).map(i -> i * 10)
)
.expectNext(10)
.expectNext(20)
.expectNext(30)
.verifyComplete();
}
@Test
void testFluxError() {
StepVerifier.create(
Flux.just(1, 2, 0).map(i -> 10 / i)
)
.expectNext(10)
.expectNext(5)
.expectError(ArithmeticException.class)
.verify();
}
@Test
void testWithVirtualTime() {
StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofHours(1)).take(3))
.thenAwait(Duration.ofHours(1))
.expectNext(0L)
.thenAwait(Duration.ofHours(1))
.expectNext(1L)
.thenAwait(Duration.ofHours(1))
.expectNext(2L)
.verifyComplete();
}
十一、最佳实践总结
| # | 实践 | 说明 |
|---|---|---|
| 1 | 不要 block() | 在响应式链中调用 block() 会阻塞线程,破坏非阻塞模型 |
| 2 | 不要在 Service 层 subscribe | 返回 Mono/Flux 给上层,让框架或最终消费者决定订阅时机 |
| 3 | 阻塞代码用 boundedElastic 包装 | Mono.fromCallable(...).subscribeOn(Schedulers.boundedElastic()) |
| 4 | flatMap 中隔离错误 | 对每个子 Publisher 添加 onErrorResume,避免一个失败终止整个流 |
| 5 | 无限流必须设终止条件 | take(n) / timeout() / takeUntil() |
| 6 | 合理使用 limitRate | 控制预取量,避免内存溢出 |
| 7 | 用 defer 包装副作用 | 确保每次订阅都重新计算 |
| 8 | Context 替代 ThreadLocal | 响应式链中 ThreadLocal 不可靠,使用 Reactor Context |
十二、总结
文本
┌─────────────────────────────────────────────────────────────┐
│ Reactive Streams │
│ Publisher<T> │
│ ┌────────────┐ │
│ │ │ │
│ ┌─────┴────┐ ┌───┴────┐ │
│ │ Flux<T> │ │ Mono<T>│ │
│ │ 0..N │ │ 0..1 │ │
│ └──────────┘ └────────┘ │
│ │
│ 创建 → 转换 → 过滤 → 组合 → 聚合 → 调度 → 错误处理 → 订阅 │
│ │
│ 核心原则: │
│ • 惰性求值:subscribe 才执行 │
│ • 声明式:描述"做什么"而非"怎么做" │
│ • 背压:下游控制上游速率 │
│ • 不可变:操作符返回新实例 │
└─────────────────────────────────────────────────────────────┘
Flux 与 Mono 是 Project Reactor 的核心抽象,它们将异步数据流统一为可组合、可变换、可背压的声明式管道。掌握这两种类型的语义与操作方式,是构建高性能响应式应用的必经之路。