一、Reactor简介
Reactor 是由 Pivotal(Spring 团队)主导的响应式编程库,是 Java 响应式流(Reactive Streams)规范的重要实现之一。
它是 Spring WebFlux 的核心底层库,广泛用于构建高性能、非阻塞、异步的数据流应用。
主要特点:
- 基于事件驱动和数据流
- 支持背压(Backpressure)
- 高性能、低延迟
- 易于与 Spring WebFlux、Netty 集成
二、核心概念
2.1 Publisher & Subscriber
- Publisher:数据源,发布数据流
- Subscriber:订阅数据流,处理数据
- Subscription:管理订阅关系和数据请求
2.2 Flux & Mono
- Mono:0或1个元素的数据流(类似 Optional/Promise)
- Flux:0到N个元素的数据流(类似 List/Stream)
Reactor 的所有操作符和流程都是基于 Flux/Mono 构建。
三、基本用法
3.1 创建 Mono 和 Flux
Mono<String> mono = Mono.just("Hello Reactor");
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5);
3.2 操作符(Operators)
- map:转换数据
- filter:过滤数据
- flatMap:异步转换
- concat/merge/zip:流组合
- delay:延迟处理
- doOnNext/doOnError:钩子函数
示例:
Flux<Integer> flux = Flux.range(1, 10)
.filter(i -> i % 2 == 0)
.map(i -> i * i)
.doOnNext(i -> System.out.println("处理:" + i));
3.3 订阅(subscribe)
flux.subscribe(
data -> System.out.println("收到数据:" + data),
error -> System.err.println("发生错误:" + error),
() -> System.out.println("处理完成")
);
四、异步与线程调度
4.1 Scheduler
Reactor 支持指定线程池/调度器:
- Schedulers.parallel():并行线程池
- Schedulers.elastic():弹性线程池,适合IO
- Schedulers.single():单线程
- Schedulers.immediate():当前线程
示例:
Flux.just(1, 2, 3)
.publishOn(Schedulers.parallel())
.map(i -> i * 2)
.subscribe(System.out::println);
4.2 异步操作
配合 WebClient、数据库驱动等实现非阻塞IO:
WebClient client = WebClient.create();
Mono<String> response = client.get()
.uri("http://example.com")
.retrieve()
.bodyToMono(String.class);
response.subscribe(System.out::println);
五、背压(Backpressure)机制
Reactor 遵循 Reactive Streams 规范,支持背压,防止下游消费慢导致内存溢出。
- request(n):订阅者可主动请求数据量
- limitRate():限制每次下游请求的数据量
flux.limitRate(5).subscribe(...);
六、错误处理
- onErrorResume:发生错误时切换到备用流
- onErrorReturn:发生错误时返回默认值
- retry:自动重试
示例:
flux.map(i -> 10 / i)
.onErrorResume(e -> Flux.just(-1))
.subscribe(System.out::println);
七、组合与聚合
- zip:多个流组合
- merge:合并流
- concat:顺序拼接流
- collectList/collectMap:聚合为集合
示例:
Flux<Integer> f1 = Flux.just(1,2,3);
Flux<Integer> f2 = Flux.just(4,5,6);
Flux<Integer> zipped = Flux.zip(f1, f2, (a, b) -> a + b);
zipped.subscribe(System.out::println);
八、与Spring WebFlux集成
Spring WebFlux 完全基于 Reactor,控制器方法可直接返回 Mono/Flux:
@RestController
public class DemoController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Hello, WebFlux!");
}
}
九、常见问题与调试技巧
| 问题类型 | 现象/报错 | 排查建议 |
|---|---|---|
| subscribe未触发 | 没有输出 | 检查是否调用了 subscribe |
| 阻塞操作 | 性能低下 | 避免阻塞方法(如 Thread.sleep) |
| 线程切换异常 | 数据不在预期线程 | 检查 publishOn/subscribeOn 使用 |
| 背压失效 | 内存飙升 | 合理设置 limitRate/request |
| 错误未捕获 | 程序崩溃 | 用 onErrorResume/onErrorReturn |
十、企业实战与最佳实践
- 所有 IO 操作都应异步,避免阻塞线程
- 链式操作符尽量简洁,复杂流程可拆分多个流
- 错误处理必须完善,避免流异常导致系统崩溃
- 善用调度器,合理分配计算和 IO 线程
- 结合 Spring WebFlux,实现高并发、低延迟的 Web API
- 监控与日志,可用 blockHound 检查阻塞代码,Reactor 提供丰富日志钩子
十一. 高级操作符与流控制
1. flatMap 与 concatMap
- flatMap:并发(异步)地把每个元素映射为一个 Publisher,结果可能乱序。
- concatMap:顺序地映射,每个 Publisher 完成后再处理下一个,保证顺序。
Flux.just(1,2,3)
.flatMap(i -> Mono.just(i * 2).delayElement(Duration.ofMillis(100)))
.subscribe(System.out::println); // 并发,结果可能乱序
Flux.just(1,2,3)
.concatMap(i -> Mono.just(i * 2).delayElement(Duration.ofMillis(100)))
.subscribe(System.out::println); // 顺序输出
2. switchIfEmpty
流为空时切换到另一个流:
Mono.justOrEmpty(null)
.switchIfEmpty(Mono.just("默认值"))
.subscribe(System.out::println);
3. defer
延迟流创建,适合每次订阅都需要最新数据:
Mono.defer(() -> Mono.just(UUID.randomUUID().toString()))
.subscribe(System.out::println);
十二. Reactor Context(上下文传递)
Reactor 支持类似 ThreadLocal 的上下文(Context),用于在流中安全传递元数据:
Mono.deferContextual(ctx -> {
String user = ctx.get("user");
return Mono.just("当前用户:" + user);
})
.contextWrite(Context.of("user", "zhangsan"))
.subscribe(System.out::println);
注意:Context 只在流内部有效,不能直接跨线程或全局共享。
十三. 阻塞与非阻塞陷阱
1. block()/toFuture()
虽然 Reactor 支持 .block()、.toFuture().get() 等阻塞操作,但在响应式编程中应尽量避免。
阻塞会占用线程池,降低并发和吞吐。
最佳实践:
- 只在测试或启动阶段用 block()
- 生产代码全链路保持异步
2. 检查阻塞代码
可用 BlockHound 检查项目中是否有阻塞点:
BlockHound.install();
十四. 流重试与超时
1. 重试操作
Mono.just(1)
.map(i -> 10 / (i - 1)) // 除零异常
.retry(3)
.onErrorReturn(-1)
.subscribe(System.out::println);
2. 超时处理
Mono.delay(Duration.ofSeconds(5))
.timeout(Duration.ofSeconds(2))
.onErrorReturn(-1L)
.subscribe(System.out::println);
十五. 热流与冷流
- 冷流(Cold Stream):每次订阅都会重新发出数据(如 Flux.just、Mono.just)
- 热流(Hot Stream):数据源不断推送,所有订阅者共享(如 Flux.interval、连接到消息队列)
热流示例
Flux<Long> hot = Flux.interval(Duration.ofSeconds(1)).share();
hot.subscribe(i -> System.out.println("A: " + i));
Thread.sleep(3000);
hot.subscribe(i -> System.out.println("B: " + i));
B 订阅时不会收到之前的数据,只能看到实时数据。
十六. 与数据库、消息队列集成
1. R2DBC(响应式数据库)
配合 R2DBC 驱动实现非阻塞数据库访问:
Mono<User> userMono = databaseClient
.sql("SELECT * FROM user WHERE id = :id")
.bind("id", 123)
.map(row -> new User(row.get("id", Integer.class), row.get("name", String.class)))
.one();
2. 响应式消息队列
如与 Kafka、RabbitMQ 响应式客户端结合,处理高吞吐消息流:
Flux<Message> messages = receiver.receive();
messages.subscribe(msg -> process(msg));
十七. 性能优化与监控
1. 调度器优化
- 计算密集型用
Schedulers.parallel() - IO密集型用
Schedulers.boundedElastic() - 避免主线程阻塞
2. 流量控制与背压
- 合理设置
limitRate() - 关注下游消费速度,防止内存堆积
3. 监控与指标
- 配合 Micrometer、Prometheus 采集 Reactor 指标
- 关键流加日志、异常打点
十八. 项目集成与最佳实践
- 推荐结合 Spring WebFlux 构建高并发 REST API
- 所有 IO(数据库、消息队列、HTTP)都用响应式客户端
- 业务逻辑拆分为小流,便于测试和维护
- 关键链路加异常处理和超时保护
十九. 典型场景代码片段
1. 并发聚合
Flux<Integer> ids = Flux.just(1,2,3,4);
Flux<User> users = ids.flatMap(id -> userService.getUserById(id));
users.collectList().subscribe(list -> System.out.println(list));
2. 文件上传异步处理
@PostMapping("/upload")
public Mono<String> upload(FilePart file) {
return file.content()
.map(dataBuffer -> processBuffer(dataBuffer))
.reduce((a, b) -> combine(a, b))
.map(result -> "处理完成:" + result);
}
二十. 响应式设计模式
1. 响应式流水线(Reactive Pipeline)
- 将数据流按业务分层拆解,每层用 map/flatMap/filter 等操作符处理,形成清晰的流水线。
- 便于各环节独立测试、复用与监控。
示例:
Flux.just("A", "B", "C")
.map(this::step1)
.flatMap(this::step2Async)
.filter(this::step3)
.subscribe(this::finalStep);
2. Saga/补偿事务模式
- 微服务架构下,响应式流可实现 Saga 模式,链式调用各服务,出错时用 onErrorResume 执行补偿操作。
- 适用于分布式事务、支付、订单等场景。
示例:
serviceA()
.flatMap(resultA -> serviceB(resultA))
.onErrorResume(e -> compensateA())
.subscribe();
3. 响应式防抖与节流
- 防抖(debounce):短时间内只处理最后一个事件
- 节流(throttle):定时处理事件,防止高频流量冲击
Flux.interval(Duration.ofMillis(100))
.debounce(Duration.ofSeconds(1))
.subscribe(System.out::println);
二十一. 调试与测试
1. 日志与信号追踪
- 使用
log()操作符输出流信号,便于排查问题
flux.log().subscribe();
- 可设置日志级别,观察 onSubscribe、onNext、onError、onComplete 等信号
2. StepVerifier 响应式单元测试
Reactor 提供 StepVerifier 进行流式断言:
StepVerifier.create(Flux.just(1, 2, 3).map(i -> i * 2))
.expectNext(2, 4, 6)
.expectComplete()
.verify();
3. 检查阻塞代码
- 使用 BlockHound 检查项目是否有阻塞方法被错误地写入响应式流中。
二十二. 与阻塞代码集成
1. 封装阻塞调用
- 用
Schedulers.boundedElastic()调度阻塞调用,避免卡住响应式线程。
Mono.fromCallable(() -> blockingMethod())
.subscribeOn(Schedulers.boundedElastic())
.subscribe(System.out::println);
2. 传统接口适配
- 可用
Mono.create()、Flux.create()包装回调式/事件式接口。
二十三. 内存与资源管理
1. 资源释放
- 对于文件、连接等资源,使用
usingWhen或doFinally保障释放。
Mono.using(
() -> openResource(),
resource -> Mono.just(resource.read()),
resource -> resource.close()
)
2. 大数据流分片处理
- 使用
window、buffer操作符将大流拆分为小批次,防止内存溢出。
Flux.range(1, 10000)
.buffer(100)
.flatMap(batch -> processBatch(batch))
.subscribe();
二十四. 常见陷阱与误区
| 问题类型 | 现象/后果 | 解决建议 |
|---|---|---|
| 忘记 subscribe | 不会触发数据流 | 反复检查是否有 subscribe 调用 |
| 阻塞操作混用 | 性能下降、线程被占满 | 阻塞代码一定要用 boundedElastic 调度 |
| 多线程数据竞争 | 并发场景下数据错乱 | 尽量无状态,必要时加锁或用同步机制 |
| 内存泄漏 | 长时间运行后内存增长 | 检查流的终止条件、资源释放 |
| 背压未处理 | 消费速度跟不上生产速度 | 合理设置 limitRate、buffer、onBackpressure |
| 错误未捕获 | 程序崩溃或日志不全 | 用 onErrorResume、onErrorReturn 兜底 |
二十五. 企业实战经验与建议
- 全链路异步:只要有阻塞就会拖慢整个响应式链,所有 IO 操作都用响应式客户端。
- 模块化设计:复杂流拆分为多个小流,便于维护和单元测试。
- 链路监控:关键流加日志和埋点,方便排查问题和性能分析。
- 异常策略:对每个链路都加 onErrorResume/onErrorContinue,避免单点故障影响全局。
- 流量控制:高并发场景合理用 buffer、window、debounce 等操作符,保护下游系统。
- 与 Spring WebFlux 结合:推荐用 WebFlux + R2DBC + 响应式消息队列,实现端到端的非阻塞架构。
- 团队培训:响应式思维和调试方法需团队普及,避免传统阻塞思路带来的隐患。
二十六. 典型架构图
+---------+ +------------+ +------------------+ +-------------+
| 客户端 | <--> | WebFlux API| <--> | Reactor 业务流 | <--> | R2DBC/消息队列|
+---------+ +------------+ +------------------+ +-------------+
- 所有链路全异步、非阻塞,最大化硬件利用率和吞吐率。
二十七. 响应式微服务架构实践
1. 端到端异步
在微服务架构中,推荐每一层(前端、API 网关、业务服务、数据库、消息队列)都采用响应式方式,避免阻塞点成为瓶颈。
- API 网关用 Spring Cloud Gateway(基于 WebFlux)
- 业务服务用 Spring WebFlux + Reactor
- 数据层用 R2DBC 或响应式 NoSQL 驱动
- 消息层用 Reactor Kafka/RabbitMQ
2. 服务间通信
- 使用 WebClient 进行服务间 HTTP 异步调用
- 推荐用异步消息队列(如 Kafka、RabbitMQ)解耦服务
WebClient.create()
.get().uri("http://user-service/user/{id}", id)
.retrieve().bodyToMono(User.class)
.subscribe(user -> ...);
3. 分布式事务
- 用响应式 Saga 模式实现分布式补偿事务(见前文)
- 利用 onErrorResume、retry、timeout 等操作符处理跨服务异常
二十八. 与 RxJava 的对比
| 维度 | Reactor | RxJava |
|---|---|---|
| 标准规范 | 完全遵循 Reactive Streams | 部分支持 |
| 数据类型 | Flux/Mono | Observable/Single/Flowable |
| 背压支持 | 内建,所有流支持 | 仅 Flowable 支持 |
| Spring 集成 | WebFlux 标准库 | 需适配 |
| 性能优化 | 专为服务器场景优化 | 更通用,适合客户端 |
| 社区活跃度 | Spring 官方维护 | 独立社区 |
| API 一致性 | 更简洁统一 | 更丰富但多样 |
结论:服务器端推荐用 Reactor,客户端或已有 RxJava 经验可用 RxJava。
二十九. 典型业务场景最佳实践
1. 高并发文件流处理
Flux<DataBuffer> fileFlux = filePart.content();
fileFlux
.flatMap(buffer -> processBufferAsync(buffer))
.doOnError(e -> log.error("文件处理异常", e))
.subscribe();
2. 实时数据推送(如 WebSocket)
Flux<String> liveData = Flux.interval(Duration.ofSeconds(1))
.map(i -> "实时数据:" + i);
webSocketSession.send(liveData.map(session::textMessage)).subscribe();
3. 大数据批处理
Flux.range(1, 100000)
.buffer(1000)
.flatMap(batch -> batchProcessAsync(batch))
.subscribe();
4. 聚合/分组统计
Flux<User> users = ...;
users.groupBy(User::getRegion)
.flatMap(group -> group.count().map(count -> group.key() + ":" + count))
.subscribe(System.out::println);
三十. 极致性能优化技巧
1. 减少上下文切换
- 避免频繁使用 publishOn/subscribeOn
- 在 IO/计算密集场景用合适的 Scheduler,减少线程切换
2. 批量操作优先
- 用 buffer、window 批量处理,提升吞吐率和资源利用率
3. 内存池与复用
- 对于频繁创建的对象(如 DataBuffer),尽量用池化和复用,减少 GC 压力
4. 监控与自适应调优
- 配合 Micrometer、Prometheus 实时采集流量、延迟、错误指标
- 根据监控结果动态调整背压参数、线程池大小等
三十一. 响应式编程常见误区总结
| 误区 | 后果/表现 | 正确做法 |
|---|---|---|
| 在响应式流中写阻塞代码 | 性能急剧下降,线程耗尽 | 用 fromCallable + boundedElastic |
| 忘记处理错误 | 程序崩溃或无响应 | 用 onErrorResume/onErrorReturn |
| 大流无背压 | 内存溢出、OOM | 用 limitRate、buffer、window |
| subscribe未调用 | 无任何效果 | 最终都要有 subscribe 或返回给框架 |
| 误用 block() | 阻塞主线程,吞吐下降 | 仅在启动或测试阶段用 block() |
三十二. 推荐开源项目与工具
三十三. 结语
Reactor 编程是现代 Java 后端开发的核心技术之一。它不仅能显著提升系统的并发能力和资源利用率,还能让架构更简洁、易扩展。
但响应式编程也有学习曲线和调试难度,建议团队统一技术规范、加强测试和监控,逐步推进业务核心链路的响应式改造。
Java Reactor编程核心解析
2640

被折叠的 条评论
为什么被折叠?



