Reactor 深度解析:响应式编程的「核反应堆」是如何工作的?
一、Reactor 是谁?为什么 Spring 选它?
Reactor 是一个基于 Reactive Streams 规范 的 JVM 响应式库,相当于响应式世界的「涡轮增压引擎」。Spring WebFlux 默认集成 Reactor,而不是 RxJava,因为:
- 专为 Java 8+ 优化 :深度整合
CompletableFuture
和Stream
的语法糖。 - 背压原生支持:从设计之初就遵循 Reactive Streams 标准。
- Spring 亲儿子:Pivotal(Spring 母公司)亲自维护,生态无缝兼容。
💡 类比:
- RxJava 是「瑞士军刀」,功能多但复杂;
- Reactor 是「精工匕首」,专注高频核心场景。
二、核心模型:Mono 与 Flux 的「量子纠缠」
1. Mono:单身贵族的哲学
- 定义:0 或 1 个元素的异步序列。
- 经典场景:查询单个数据库记录、HTTP 请求响应。
java
Mono<User> userMono = userRepository.findById(1);
// 相当于 Optional<User>,但带异步超能力
2. Flux:后宫团的流量密码
- 定义:0 到 N 个元素的异步序列。
- 经典场景:消息流、批量查询、SSE(Server-Sent Events)。
java
Flux<Order> orders = orderRepository.findAll();
// 相当于 List<Order> 的异步动态版
量子纠缠现象 :
通过 flatMap
、concatMap
等操作符,Mono 和 Flux 可以互相转换:
java
Flux<User> users = Flux.just(1, 2, 3)
.flatMap(id -> userRepository.findById(id)); // Mono → Flux
三、线程模型:Schedulers 的「影分身之术」
Reactor 的异步魔法依赖于 Scheduler(调度器),关键角色:
调度器类型 | 作用 | 适用场景 |
---|---|---|
Schedulers.immediate() |
当前线程执行(无切换) | 快速无阻塞操作 |
Schedulers.single() |
全局单线程池 | 低并发轻任务 |
Schedulers.parallel() |
固定大小线程池(CPU 核数) | CPU 密集型计算 |
Schedulers.boundedElastic() |
弹性线程池(带队列限制) | 阻塞操作(如 JDBC、旧式 API) |
Schedulers.fromExecutor() |
自定义线程池 | 集成现有基础设施 |
操作符搭配使用:
java
Flux.range(1, 10)
.publishOn(Schedulers.parallel()) // 下游操作切换到并行线程池
.map(i -> i * 2) // 在 parallel 线程执行
.subscribeOn(Schedulers.boundedElastic()) // 源头执行线程
.subscribe();
⚠️ 陷阱 :
subscribeOn
只影响源头,publishOn
影响下游------顺序错了可能白切换!
四、核心操作符:Reactor 的「忍术大全」
1. 创建流(Creation Operators)
java
Mono.just("Hello") // 静态值
Mono.fromFuture(CompletableFuture.supplyAsync(() -> "Hi")) // 兼容 Future
Flux.interval(Duration.ofSeconds(1)) // 定时发射(0,1,2...)
Flux.fromStream(IntStream.range(1,5).boxed()) // 懒加载 Stream
2. 转换流(Transformation Operators)
java
Flux.just("apple", "banana")
.map(String::toUpperCase) // 同步映射
.flatMap(s -> Mono.just(s + "!")) // 异步展开(Mono→Flux)
.concatMap(s -> externalApiCall(s)) // 保证顺序的异步展开
3. 过滤控制(Filtering & Control)
java
Flux.range(1, 100)
.filter(i -> i % 2 == 0) // 过滤偶数
.take(5) // 只取前5个
.timeout(Duration.ofSeconds(3)) // 超时熔断
4. 错误处理(Error Handling)
java
Flux.just(1, 0, 2)
.map(i -> 10 / i) // 会除零异常
.onErrorResume(e -> Flux.just(-1)) // 捕获后返回备用值
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) // 指数退避重试
五、高级特性:Reactor 的「奥义技」
1. 热发布 vs 冷发布
-
冷发布(Cold Publisher) :每次订阅重新生成数据(如数据库查询)。
javaFlux<String> cold = Flux.defer(() -> Flux.fromIterable(fetchData()));
-
热发布(Hot Publisher) :数据共享给所有订阅者(如股票行情)。
javaConnectableFlux<Integer> hot = Flux.range(1, 10).publish(); hot.connect(); // 手动启动数据流
2. Context:跨阶段的「暗黑数据」
Reactor 的 Context
可以隐式传递数据(替代 ThreadLocal
):
java
Mono<String> result = Mono.deferContextual(ctx -> {
String traceId = ctx.get("traceId");
return Mono.just("Trace: " + traceId);
}).contextWrite(Context.of("traceId", "123")); // 注入上下文
3. 测试:Reactor Test 工具包
java
StepVerifier.create(Flux.just(1, 2, 3).expectNext(1, 2, 3).verifyComplete();
StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofDays(1)))
.thenAwait(Duration.ofDays(1)) // 时间快进
.expectNext(0L)
.verifyComplete();
六、性能调优:从「拖拉机」到「超跑」
1. 避免阻塞操作
java
// 错误示范:阻塞破坏响应式链
Flux.range(1, 10)
.map(i -> {
Thread.sleep(1000); // 阻塞炸弹!
return i;
});
// 正确姿势:用 boundedElastic 隔离阻塞
Flux.range(1, 10)
.publishOn(Schedulers.boundedElastic())
.map(i -> blockingIoCall(i));
2. 合理配置线程池
java
// 自定义弹性线程池(限制最大线程数)
Scheduler customScheduler = Schedulers.newBoundedElastic(
10, // 最大线程数
100, // 任务队列容量
"custom-pool");
// 使用后记得关闭(防止内存泄漏)
customScheduler.dispose();
3. 监控与诊断
-
启用调试模式 :
javaHooks.onOperatorDebug(); // 打印详细操作符日志
-
Micrometer 集成 :
javaFlux.just(1, 2, 3) .name("myFlux") // 命名指标 .metrics() // 暴露给 Prometheus .subscribe();
七、面试直通车:Reactor 高频考题
Q1:map
和 flatMap
的区别?
map
:同步转换,输入 T 输出 R(1:1)。flatMap
:异步转换,输入 T 输出Publisher<R>
(1:N,可能乱序)。
Q2:subscribeOn
和 publishOn
的区别?
subscribeOn
:控制整个链的订阅执行线程(源头生效)。publishOn
:控制下游操作的执行线程(位置敏感)。
Q3:如何实现响应式超时重试?
java
Flux.just("data")
.timeout(Duration.ofSeconds(3))
.retryWhen(Retry.fixedDelay(2, Duration.ofSeconds(1)));
八、总结:Reactor 的「终极奥义」
- 核心原则:异步非阻塞 + 背压控制 = 高吞吐低延迟。
- 黄金搭配 :
Mono/Flux
+Schedulers
+WebClient
= 服务间调用R2DBC
+Reactive Repository
= 数据库交互
- 避坑箴言 :
- 不要阻塞响应式链(否则性能反降)。
- 理解冷热发布区别(避免数据意外共享)。
- 永远考虑背压(除非你想半夜被告警电话叫醒)。
🚀 Reactor 不是银弹,但用好了就是「分布式系统的光剑」!