Java深入解析篇十九之Project Reactor
一、Project Reactor 概述
1.1 什么是 Project Reactor
Project Reactor 是一个基于 Reactive Streams 规范的响应式编程库,是 Spring WebFlux 的底层基础。它提供了两个核心类型 Mono 和 Flux,用于构建非阻塞、异步的应用程序。
1.2 核心依赖
xml
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.6.0</version>
<scope>test</scope>
</dependency>
1.3 Reactive Streams 规范
Reactive Streams 定义了四个核心接口:
java
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}
public interface Subscriber<T> {
void onSubscribe(Subscription subscription);
void onNext(T item);
void onError(Throwable throwable);
void onComplete();
}
public interface Subscription {
void request(long n); // 背压:请求n个元素
void cancel();
}
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
1.4 响应式编程 vs 传统编程
| 特性 | 传统命令式 | 响应式 |
|---|---|---|
| 执行模型 | 同步阻塞 | 异步非阻塞 |
| 线程使用 | 一请求一线程 | 事件循环,少量线程 |
| 背压 | 无 | 内置支持 |
| 错误处理 | try-catch | 操作符链式处理 |
| 组合方式 | 嵌套回调 | 声明式操作符 |
二、Mono(0或1个元素的异步序列)
2.1 基本概念
Mono<T> 表示一个最多包含一个元素的异步序列,类似于 CompletableFuture<T>,但支持响应式操作符和背压。
2.2 创建 Mono
java
// 从值创建
Mono<String> mono1 = Mono.just("Hello Reactor");
// 空Mono
Mono<String> mono2 = Mono.empty();
// 错误Mono
Mono<String> mono3 = Mono.error(new RuntimeException("出错了"));
// 从Callable创建(延迟执行)
Mono<String> mono4 = Mono.fromCallable(() -> {
// 可以抛出受检异常
return Files.readString(Path.of("data.txt"));
});
// 从Supplier创建
Mono<String> mono5 = Mono.fromSupplier(() -> "延迟计算的值");
// 从CompletableFuture创建
Mono<String> mono6 = Mono.fromFuture(() -> CompletableFuture.supplyAsync(() -> "异步结果"));
// defer:每次订阅时重新创建
Mono<String> mono7 = Mono.defer(() -> Mono.just("每次订阅都是新的: " + System.nanoTime()));
2.3 Mono 常用操作
java
Mono.just("Hello")
.map(s -> s.toUpperCase()) // 同步转换
.flatMap(s -> saveToDatabase(s)) // 异步转换,返回另一个Mono
.filter(s -> s.length() > 3) // 不满足条件变为空Mono
.defaultIfEmpty("默认值") // 空Mono时提供默认值
.switchIfEmpty(Mono.just("备用值")) // 空Mono时切换到另一个Mono
.zipWhen(s -> getRelatedData(s)) // 与另一个Mono组合
.doOnSuccess(s -> log.info("成功: {}", s))
.doOnError(e -> log.error("失败", e))
.subscribe(result -> System.out.println("结果: " + result));
2.4 Mono 的终止操作
java
// 阻塞获取(仅用于测试或非响应式环境)
String value = Mono.just("test").block();
String valueWithTimeout = Mono.just("test").block(Duration.ofSeconds(5));
// 转为CompletableFuture
CompletableFuture<String> future = Mono.just("test").toFuture();
// 订阅(触发执行)
Mono.just("test")
.subscribe(
item -> System.out.println("onNext: " + item),
error -> System.err.println("onError: " + error),
() -> System.out.println("onComplete")
);
三、Flux(0到N个元素的异步序列)
3.1 基本概念
Flux<T> 表示一个包含 0 到 N 个元素的异步序列,支持背压。元素按顺序发射,以 onComplete 或 onError 信号终止。
3.2 创建 Flux
java
// 从多个值创建
Flux<String> flux1 = Flux.just("A", "B", "C", "D");
// 从集合创建
Flux<String> flux2 = Flux.fromIterable(List.of("X", "Y", "Z"));
// 从数组创建
Flux<String> flux3 = Flux.fromArray(new String[]{"1", "2", "3"});
// 范围序列
Flux<Integer> flux4 = Flux.range(1, 100); // 1到100
// 时间间隔(每秒发射一个递增数字)
Flux<Long> flux5 = Flux.interval(Duration.ofSeconds(1));
// 空Flux和错误Flux
Flux<String> flux6 = Flux.empty();
Flux<String> flux7 = Flux.error(new IllegalStateException("流错误"));
// 使用generate(同步、逐个生成)
Flux<Integer> flux8 = Flux.generate(
() -> 0, // 初始状态
(state, sink) -> {
sink.next(state);
if (state == 10) {
sink.complete();
}
return state + 1; // 返回新状态
}
);
// 使用create(异步、可多线程发射)
Flux<String> flux9 = Flux.create(sink -> {
// 可以注册监听器
messageListener.onMessage(msg -> sink.next(msg));
messageListener.onComplete(() -> sink.complete());
messageListener.onError(e -> sink.error(e));
// 取消时清理
sink.onCancel(() -> messageListener.close());
}, FluxSink.OverflowStrategy.BUFFER);
3.3 Flux 的终止操作
java
// 阻塞获取第一个/最后一个元素
String first = Flux.just("A", "B", "C").blockFirst();
String last = Flux.just("A", "B", "C").blockLast();
// 转为集合(阻塞)
List<String> list = Flux.just("A", "B", "C").collectList().block();
// 转为Stream(阻塞迭代)
Flux.just("A", "B", "C").toStream().forEach(System.out::println);
// 订阅
Flux.range(1, 5)
.subscribe(
item -> System.out.println("收到: " + item),
error -> System.err.println("错误: " + error),
() -> System.out.println("完成")
);
四、创建操作符详解
4.1 just / from 系列
java
// just:直接包装已有值
Mono<String> m1 = Mono.just("value");
Flux<Integer> f1 = Flux.just(1, 2, 3, 4, 5);
// fromIterable:从Iterable创建
Flux<String> f2 = Flux.fromIterable(Arrays.asList("a", "b", "c"));
// fromStream:从Java Stream创建(只能消费一次)
Flux<String> f3 = Flux.fromStream(() ->
Files.lines(Path.of("file.txt")) // 使用Supplier保证可重复订阅
);
// fromFuture:从CompletableFuture创建
Mono<String> m2 = Mono.fromFuture(() -> httpClient.getAsync("/api/data"));
4.2 defer(延迟创建)
java
// 错误示例:值在创建时就确定了
String timestamp = Instant.now().toString();
Mono<String> wrong = Mono.just(timestamp); // 所有订阅者看到相同时间
// 正确示例:每次订阅时重新计算
Mono<String> correct = Mono.defer(() -> Mono.just(Instant.now().toString()));
// 实际场景:根据条件选择不同的数据源
Mono<User> findUser(Long id) {
return Mono.defer(() -> {
if (cache.contains(id)) {
return Mono.just(cache.get(id));
}
return userRepository.findById(id)
.doOnNext(user -> cache.put(id, user));
});
}
4.3 generate(同步生成)
java
// 生成斐波那契数列
Flux<Long> fibonacci = Flux.generate(
() -> new long[]{0, 1}, // 初始状态
(state, sink) -> {
sink.next(state[0]);
long next = state[0] + state[1];
state[0] = state[1];
state[1] = next;
if (state[0] > 1_000_000) {
sink.complete();
}
return state;
}
);
// 带清理回调
Flux<String> withCleanup = Flux.generate(
() -> new BufferedReader(new FileReader("data.csv")),
(reader, sink) -> {
try {
String line = reader.readLine();
if (line == null) {
sink.complete();
} else {
sink.next(line);
}
} catch (IOException e) {
sink.error(e);
}
return reader;
},
reader -> { // 状态清理
try { reader.close(); } catch (IOException ignored) {}
}
);
4.4 create(异步生成)
java
// 包装回调式API为Flux
Flux<ServerSentEvent> wrapEventListener(EventSource source) {
return Flux.create(sink -> {
EventListener listener = new EventListener() {
@Override
public void onEvent(Event event) {
sink.next(new ServerSentEvent(event.getData()));
}
@Override
public void onError(Exception e) {
sink.error(e);
}
@Override
public void onClose() {
sink.complete();
}
};
source.addEventListener(listener);
// 订阅取消时移除监听器
sink.onDispose(() -> source.removeEventListener(listener));
}, FluxSink.OverflowStrategy.BUFFER);
}
// 多线程发射(create允许从多个线程调用sink.next)
Flux<Integer> multiThreaded = Flux.create(sink -> {
ExecutorService executor = Executors.newFixedThreadPool(4);
AtomicInteger counter = new AtomicInteger(0);
for (int i = 0; i < 4; i++) {
executor.submit(() -> {
while (!sink.isCancelled()) {
int val = counter.incrementAndGet();
if (val > 100) {
sink.complete();
return;
}
sink.next(val);
}
});
}
sink.onDispose(executor::shutdown);
});
五、转换操作符
5.1 map(同步转换)
java
Flux.just("hello", "reactor", "world")
.map(String::toUpperCase) // 同步一对一转换
.map(s -> s.length()) // 可以链式多次map
.subscribe(len -> System.out.println("长度: " + len));
// 输出: 长度: 5, 长度: 7, 长度: 5
5.2 flatMap(异步转换,不保序)
java
// flatMap:将每个元素转换为一个Publisher,并发订阅,结果交错
Flux.just("user1", "user2", "user3")
.flatMap(username -> webClient.get()
.uri("/api/users/{name}", username)
.retrieve()
.bodyToMono(User.class)) // 返回Mono<User>
.subscribe(user -> System.out.println("收到: " + user.getName()));
// 注意:输出顺序不确定,取决于各请求完成时间
// flatMap带并发度控制
Flux.range(1, 100)
.flatMap(i -> processAsync(i), 10) // 最多10个并发
.subscribe();
5.3 concatMap(异步转换,保序)
java
// concatMap:顺序订阅内部Publisher,保证输出顺序
Flux.just("first", "second", "third")
.concatMap(s -> Mono.delay(Duration.ofMillis(100))
.thenReturn(s.toUpperCase()))
.subscribe(s -> System.out.println(s));
// 输出顺序一定是: FIRST, SECOND, THIRD
// 适用场景:需要保证顺序的数据库写入
Flux.fromIterable(orders)
.concatMap(order -> orderRepository.save(order)) // 按顺序保存
.subscribe();
5.4 switchMap(取消前一个内部流)
java
// switchMap:新元素到来时取消前一个未完成的内部流
// 典型场景:搜索框自动补全
Flux<String> searchInput = getSearchInputFlux();
searchInput
.switchMap(keyword -> {
if (keyword.isEmpty()) return Flux.empty();
return searchService.search(keyword); // 新输入会取消旧搜索
})
.subscribe(results -> updateUI(results));
5.5 转换操作符对比
| 操作符 | 保序 | 并发 | 适用场景 |
|---|---|---|---|
| map | 是 | 无 | 同步转换 |
| flatMap | 否 | 是 | 并发异步操作,不关心顺序 |
| concatMap | 是 | 否 | 顺序异步操作 |
| switchMap | 否 | 取消旧的 | 只关心最新结果 |
| flatMapSequential | 是 | 是 | 并发执行但按序输出 |
六、过滤操作符
6.1 filter
java
Flux.range(1, 20)
.filter(n -> n % 2 == 0) // 只保留偶数
.subscribe(System.out::println);
// 输出: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
6.2 take / skip
java
Flux<Integer> numbers = Flux.range(1, 100);
// take:取前N个
numbers.take(5).subscribe(); // 1,2,3,4,5
numbers.takeLast(3).subscribe(); // 98,99,100
numbers.takeUntil(n -> n > 10).subscribe(); // 1到11
numbers.takeWhile(n -> n < 10).subscribe(); // 1到9
// skip:跳过前N个
numbers.skip(95).subscribe(); // 96,97,98,99,100
numbers.skipUntil(n -> n > 50).subscribe(); // 51到100
numbers.skipWhile(n -> n < 50).subscribe(); // 50到100
// 按时间取
Flux.interval(Duration.ofMillis(100))
.take(Duration.ofSeconds(1)) // 只取1秒内的元素
.subscribe();
6.3 distinct
java
// distinct:全局去重
Flux.just("A", "B", "A", "C", "B", "D")
.distinct()
.subscribe(); // A, B, C, D
// distinctUntilChanged:相邻去重
Flux.just("A", "A", "B", "B", "A", "C")
.distinctUntilChanged()
.subscribe(); // A, B, A, C
// 按key去重
Flux.just(new User(1, "Alice"), new User(2, "Bob"), new User(1, "Alice2"))
.distinct(User::getId)
.subscribe(); // 只保留id=1的第一个和id=2的
6.4 buffer / window
java
// buffer:将元素收集为批次(List)
Flux.range(1, 10)
.buffer(3) // 每3个一批
.subscribe(batch -> System.out.println("批次: " + batch));
// [1,2,3], [4,5,6], [7,8,9], [10]
// buffer按时间
Flux.interval(Duration.ofMillis(100))
.buffer(Duration.ofMillis(500)) // 每500ms收集一批
.subscribe(batch -> System.out.println("时间批次大小: " + batch.size()));
// window:将元素分组为子Flux
Flux.range(1, 10)
.window(3)
.flatMap(windowFlux -> windowFlux.collectList())
.subscribe(list -> System.out.println("窗口: " + list));
七、组合操作符
7.1 zip(配对组合)
java
// 两个流配对
Mono<User> userMono = getUserById(1L);
Mono<Order> orderMono = getOrderByUserId(1L);
Mono.zip(userMono, orderMono)
.map(tuple -> {
User user = tuple.getT1();
Order order = tuple.getT2();
return new UserOrderDTO(user, order);
})
.subscribe(dto -> System.out.println(dto));
// 多个流zip
Mono.zip(
getUserMono(),
getProfileMono(),
getSettingsMono(),
(user, profile, settings) -> new FullUserVO(user, profile, settings)
).subscribe();
// Flux的zip:按位置配对
Flux<String> names = Flux.just("Alice", "Bob", "Charlie");
Flux<Integer> ages = Flux.just(25, 30, 35);
Flux.zip(names, ages, (name, age) -> name + " is " + age)
.subscribe(System.out::println);
// Alice is 25, Bob is 30, Charlie is 35
7.2 merge(交错合并)
java
// merge:多个源并发订阅,谁先产生数据谁先输出
Flux<String> fastSource = Flux.just("fast1", "fast2")
.delayElements(Duration.ofMillis(50));
Flux<String> slowSource = Flux.just("slow1", "slow2")
.delayElements(Duration.ofMillis(200));
Flux.merge(fastSource, slowSource)
.subscribe(s -> System.out.println(Thread.currentThread().getName() + ": " + s));
// 输出顺序不确定,fast的可能先出
// mergeSequential:并发订阅但按源顺序输出
Flux.mergeSequential(fastSource, slowSource)
.subscribe(System.out::println);
// 一定是: fast1, fast2, slow1, slow2
7.3 concat(顺序拼接)
java
// concat:第一个流完成后才订阅第二个
Flux<String> first = Flux.just("A", "B", "C");
Flux<String> second = Flux.just("D", "E", "F");
Flux<String> third = Flux.just("G", "H", "I");
Flux.concat(first, second, third)
.subscribe(System.out::println);
// 一定是: A B C D E F G H I
// 实际场景:先查缓存,缓存没有再查数据库
Flux<Product> getProducts(String category) {
return Flux.concat(
cacheService.getProducts(category), // 先查缓存
databaseService.getProducts(category) // 缓存为空时查数据库
).distinct(Product::getId);
}
7.4 combineLatest(最新值组合)
java
// combineLatest:任一源发射时,用各源最新值组合
Flux<Long> priceUpdates = Flux.interval(Duration.ofSeconds(1));
Flux<Long> quantityUpdates = Flux.interval(Duration.ofSeconds(3));
Flux.combineLatest(priceUpdates, quantityUpdates,
(price, quantity) -> "价格:" + price + " 数量:" + quantity + " 总价:" + (price * quantity))
.subscribe(System.out::println);
// 典型场景:表单验证(任一字段变化时重新验证)
Flux<Boolean> formValid = Flux.combineLatest(
usernameField.map(s -> s.length() >= 3),
emailField.map(s -> s.contains("@")),
passwordField.map(s -> s.length() >= 8),
(validUser, validEmail, validPass) -> validUser && validEmail && validPass
);
7.5 组合操作符对比
| 操作符 | 行为 | 适用场景 |
|---|---|---|
| zip | 按位置配对,等最慢的 | 聚合多个独立请求结果 |
| merge | 交错输出,谁快谁先 | 合并多个同类数据源 |
| concat | 顺序输出,一个完了再下一个 | 有优先级的数据源 |
| combineLatest | 任一变化时用最新值组合 | 实时计算、表单验证 |
八、错误处理
8.1 onErrorReturn(返回默认值)
java
Flux.just(1, 2, 0, 4)
.map(n -> 10 / n)
.onErrorReturn(-1) // 任何错误都返回-1
.subscribe(System.out::println);
// 输出: 10, 5, -1(流终止)
// 针对特定异常类型
Flux.just(1, 2, 0, 4)
.map(n -> 10 / n)
.onErrorReturn(ArithmeticException.class, 0)
.subscribe(System.out::println);
// 带条件的默认值
Mono<User> user = findById(id)
.onErrorReturn(e -> e instanceof NotFoundException, User.anonymous());
8.2 onErrorResume(切换到备用流)
java
// 主服务失败时切换到备用服务
Mono<Data> getData() {
return primaryService.fetch()
.onErrorResume(TimeoutException.class, e -> {
log.warn("主服务超时,切换备用服务", e);
return fallbackService.fetch();
})
.onErrorResume(e -> {
log.error("所有服务不可用", e);
return Mono.just(Data.empty()); // 返回空数据
});
}
// 重试后降级
Flux<Item> getItems() {
return remoteService.getItems()
.retry(2)
.onErrorResume(e -> localCache.getItems());
}
8.3 doOnError(副作用:记录日志)
java
Mono<Order> processOrder(Long orderId) {
return orderService.getOrder(orderId)
.flatMap(this::validateOrder)
.flatMap(this::chargePayment)
.doOnError(ValidationException.class, e ->
log.warn("订单验证失败: orderId={}, reason={}", orderId, e.getMessage()))
.doOnError(PaymentException.class, e ->
log.error("支付失败: orderId={}", orderId, e))
.doOnError(e ->
metricsCounter.increment("order.errors"));
}
8.4 retry / retryWhen
java
// 简单重试
Mono<String> result = httpClient.get("/api/data")
.retry(3); // 最多重试3次(共4次尝试)
// retryWhen:高级重试策略
Mono<String> resilient = httpClient.get("/api/data")
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) // 指数退避
.maxBackoff(Duration.ofSeconds(30)) // 最大退避时间
.jitter(0.5) // 50%随机抖动
.filter(e -> e instanceof TimeoutException) // 只重试超时
.doBeforeRetry(signal ->
log.info("第{}次重试,原因: {}", signal.totalRetries() + 1, signal.failure()))
.onRetryExhaustedThrow((spec, signal) ->
new ServiceException("重试耗尽", signal.failure()))
);
// 固定延迟重试
Mono<String> fixedRetry = service.call()
.retryWhen(Retry.fixedDelay(5, Duration.ofMillis(500))
.filter(e -> e instanceof TransientException));
8.5 timeout(超时处理)
java
// 简单超时
Mono<Data> withTimeout = slowService.fetch()
.timeout(Duration.ofSeconds(5)); // 超时抛出TimeoutException
// 超时后降级
Mono<Data> withFallback = slowService.fetch()
.timeout(Duration.ofSeconds(5), Mono.just(Data.defaultData()));
// 每个元素之间的超时(Flux)
Flux<Event> events = eventSource.listen()
.timeout(Duration.ofMinutes(1)); // 1分钟内没有新元素则超时
九、调度器(Schedulers)
9.1 调度器类型
java
// parallel:固定大小线程池,用于CPU密集型任务
Scheduler parallelScheduler = Schedulers.parallel();
// 默认线程数 = CPU核心数
// boundedElastic:有界弹性线程池,用于阻塞I/O
Scheduler ioScheduler = Schedulers.boundedElastic();
// 默认最大线程数 = CPU核心数 * 10
// 默认队列容量 = 100000
// single:单线程,用于需要顺序执行的场景
Scheduler singleScheduler = Schedulers.single();
// immediate:当前线程(主要用于测试)
Scheduler immediateScheduler = Schedulers.immediate();
9.2 subscribeOn vs publishOn
java
// subscribeOn:影响订阅点和数据源头的线程
// 无论放在链的哪个位置,效果相同(只有第一次调用生效)
Flux.range(1, 5)
.map(i -> {
System.out.println("map1: " + Thread.currentThread().getName());
return i * 2;
})
.subscribeOn(Schedulers.parallel()) // 源头和map1在parallel线程
.map(i -> {
System.out.println("map2: " + Thread.currentThread().getName());
return i + 1;
})
.subscribe(i -> System.out.println("subscribe: " + Thread.currentThread().getName()));
// map1和map2都在parallel线程
// publishOn:影响下游操作符的线程
Flux.range(1, 5)
.map(i -> {
System.out.println("map1: " + Thread.currentThread().getName());
return i * 2;
})
.publishOn(Schedulers.boundedElastic()) // 从这里开始切换线程
.map(i -> {
System.out.println("map2: " + Thread.currentThread().getName());
return i + 1;
})
.subscribe(i -> System.out.println("subscribe: " + Thread.currentThread().getName()));
// map1在main线程,map2和subscribe在boundedElastic线程
9.3 包装阻塞调用
java
// 将阻塞的JDBC调用包装为响应式
Mono<List<Product>> getProductsBlocking() {
return Mono.fromCallable(() -> {
// 阻塞的JDBC操作
return jdbcTemplate.query(
"SELECT * FROM products WHERE category = ?",
new BeanPropertyRowMapper<>(Product.class),
"electronics"
);
})
.subscribeOn(Schedulers.boundedElastic()); // 必须在弹性线程池执行
}
// 包装阻塞的文件I/O
Mono<String> readFile(String path) {
return Mono.fromCallable(() -> Files.readString(Path.of(path)))
.subscribeOn(Schedulers.boundedElastic());
}
// 包装同步HTTP调用
Mono<HttpResponse> syncHttpCall(String url) {
return Mono.fromCallable(() -> {
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
return new HttpResponse(conn.getResponseCode(), readBody(conn));
})
.subscribeOn(Schedulers.boundedElastic())
.timeout(Duration.ofSeconds(10));
}
9.4 自定义调度器
java
// 自定义parallel调度器
Scheduler customParallel = Schedulers.newParallel("my-parallel", 4);
// 自定义boundedElastic调度器
Scheduler customElastic = Schedulers.newBoundedElastic(
20, // 最大线程数
1000, // 队列容量
"my-io" // 线程名前缀
);
// 从已有Executor创建
ExecutorService executor = Executors.newFixedThreadPool(8);
Scheduler fromExecutor = Schedulers.fromExecutor(executor);
// 使用完毕后释放
customParallel.dispose();
customElastic.dispose();
十、背压策略
10.1 背压概念
背压(Backpressure)是响应式流中消费者控制生产者速度的机制。当生产者发射数据的速度超过消费者处理速度时,需要背压策略来应对。
10.2 onBackpressureBuffer
java
// 无界缓冲(默认):所有未处理元素都缓存
Flux.interval(Duration.ofMillis(1))
.onBackpressureBuffer()
.publishOn(Schedulers.parallel())
.subscribe(i -> {
Thread.sleep(10); // 慢消费者
System.out.println(i);
});
// 有界缓冲:缓冲满时抛出异常
Flux.interval(Duration.ofMillis(1))
.onBackpressureBuffer(100) // 最多缓冲100个
.subscribe(slowConsumer);
// 有界缓冲 + 溢出策略
Flux.interval(Duration.ofMillis(1))
.onBackpressureBuffer(100,
dropped -> log.warn("丢弃: {}", dropped),
BufferOverflowStrategy.DROP_OLDEST) // 缓冲满时丢弃最旧的
.subscribe(slowConsumer);
10.3 onBackpressureDrop
java
// 丢弃策略:消费者来不及处理的直接丢弃
Flux.interval(Duration.ofMillis(1))
.onBackpressureDrop(dropped ->
log.debug("丢弃元素: {}", dropped))
.publishOn(Schedulers.single())
.subscribe(i -> {
Thread.sleep(50); // 慢消费者
System.out.println("处理: " + i);
});
10.4 onBackpressureLatest
java
// 只保留最新:缓冲中只保留最新的一个元素
Flux.interval(Duration.ofMillis(1))
.onBackpressureLatest() // 消费者忙时只保留最新值
.publishOn(Schedulers.single())
.subscribe(i -> {
Thread.sleep(100);
System.out.println("最新值: " + i); // 会跳过很多中间值
});
10.5 实际应用场景
java
// 场景1:实时股票行情(只关心最新价格)
Flux<StockPrice> stockFeed = getStockFeed("AAPL")
.onBackpressureLatest(); // UI只需要最新价格
// 场景2:日志收集(允许丢弃部分日志)
Flux<LogEntry> logs = collectLogs()
.onBackpressureDrop(dropped -> metrics.increment("logs.dropped"));
// 场景3:数据库批量写入(缓冲后批量处理)
Flux<Event> events = eventStream()
.onBackpressureBuffer(1000)
.buffer(100) // 每100个一批
.flatMap(batch -> database.batchInsert(batch));
十一、Context 传播
11.1 为什么需要 Context
在响应式编程中,代码会在不同线程间切换,ThreadLocal 无法可靠传递上下文信息。Reactor 提供了 Context 作为替代方案。
11.2 Context 基本用法
java
// 写入Context(从下游向上游传播)
Mono<String> withContext = Mono.deferContextual(ctx -> {
String traceId = ctx.get("traceId");
return Mono.just("处理请求, traceId=" + traceId);
})
.contextWrite(Context.of("traceId", "abc-123-def")); // 写入Context
// 多层Context
Mono<String> multiLayer = Mono.deferContextual(ctx -> {
String traceId = ctx.get("traceId");
String userId = ctx.get("userId");
return Mono.just(traceId + ":" + userId);
})
.contextWrite(Context.of("traceId", "trace-001"))
.contextWrite(Context.of("userId", "user-42"));
// 注意:contextWrite从下往上读,越靠近subscribe的优先级越高
11.3 典型应用:链路追踪
java
// 定义追踪过滤器(WebFlux WebFilter)
@Component
public class TraceIdFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String traceId = Optional.ofNullable(
exchange.getRequest().getHeaders().getFirst("X-Trace-Id"))
.orElse(UUID.randomUUID().toString());
return chain.filter(exchange)
.contextWrite(Context.of("traceId", traceId));
}
}
// 在业务代码中读取traceId
@Service
public class OrderService {
public Mono<Order> createOrder(OrderRequest request) {
return Mono.deferContextual(ctx -> {
String traceId = ctx.getOrDefault("traceId", "unknown");
log.info("[{}] 创建订单", traceId);
return orderRepository.save(new Order(request))
.flatMap(order -> paymentService.charge(order)
.thenReturn(order));
});
}
}
11.4 与 MDC 集成
java
// 使用Hooks将Context同步到MDC(用于日志框架)
@Configuration
public class ReactorMdcConfig {
@PostConstruct
public void setupMdcContextPropagation() {
Hooks.onEachOperator("mdc",
Operators.lift((scannable, subscriber) ->
new MdcContextSubscriber<>(subscriber)));
}
}
// 或者使用context-propagation库(推荐,Reactor 3.5+)
// 依赖: io.micrometer:context-propagation
// 自动将Context中的值传播到ThreadLocal/MDC
十二、Hot vs Cold 序列
12.1 Cold 序列(冷序列)
java
// Cold:每个订阅者独立获得完整数据
Flux<Integer> coldFlux = Flux.range(1, 5)
.map(i -> {
System.out.println("生成: " + i);
return i * 10;
});
coldFlux.subscribe(i -> System.out.println("订阅者A: " + i));
System.out.println("---");
coldFlux.subscribe(i -> System.out.println("订阅者B: " + i));
// 每个订阅者都会触发完整的"生成"过程
12.2 Hot 序列(热序列)
java
// 使用Sinks创建Hot序列
Sinks.Many<String> sink = Sinks.many().multicast().onBackpressureBuffer();
Flux<String> hotFlux = sink.asFlux();
// 订阅者A先订阅
hotFlux.subscribe(s -> System.out.println("A收到: " + s));
// 发射数据
sink.tryEmitNext("消息1");
sink.tryEmitNext("消息2");
// 订阅者B后订阅(错过消息1和消息2)
hotFlux.subscribe(s -> System.out.println("B收到: " + s));
sink.tryEmitNext("消息3");
sink.tryEmitComplete();
// A收到: 消息1, 消息2, 消息3
// B收到: 消息3
12.3 Cold 转 Hot
java
// share():将Cold转为Hot(引用计数,最后一个取消订阅时断开)
Flux<Long> shared = Flux.interval(Duration.ofSeconds(1))
.share(); // 多个订阅者共享同一个interval
// cache():缓存所有已发射的元素
Flux<String> cached = expensiveQuery()
.cache(); // 第一个订阅者触发查询,后续订阅者直接获得缓存
// cache(Duration):带过期时间的缓存
Flux<String> timedCache = expensiveQuery()
.cache(Duration.ofMinutes(5)); // 5分钟后重新查询
// replay():重放历史元素给新订阅者
Flux<String> replayed = sink.asFlux()
.replay(10); // 新订阅者能收到最近10个历史元素
12.4 Sinks API 详解
java
// 单值Sink(类似Mono)
Sinks.One<String> oneSink = Sinks.one();
Mono<String> mono = oneSink.asMono();
oneSink.tryEmitValue("结果");
// 多值Sink - 多播(多个订阅者)
Sinks.Many<String> multicast = Sinks.many()
.multicast()
.onBackpressureBuffer(256);
// 多值Sink - 重放(新订阅者收到历史数据)
Sinks.Many<String> replay = Sinks.many()
.replay()
.all(); // 重放所有历史
// 多值Sink - 单播(只允许一个订阅者)
Sinks.Many<String> unicast = Sinks.many()
.unicast()
.onBackpressureBuffer();
// 发射结果处理
Sinks.EmitResult result = multicast.tryEmitNext("data");
if (result.isFailure()) {
log.error("发射失败: {}", result);
}
// 或者使用emitNext(带重试策略)
multicast.emitNext("data", Sinks.EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
十三、测试(StepVerifier)
13.1 基本验证
java
import reactor.test.StepVerifier;
@Test
void testFluxElements() {
Flux<String> flux = Flux.just("A", "B", "C");
StepVerifier.create(flux)
.expectNext("A")
.expectNext("B")
.expectNext("C")
.verifyComplete(); // 验证正常完成
}
@Test
void testFluxWithPredicate() {
Flux<Integer> flux = Flux.range(1, 5).map(i -> i * 2);
StepVerifier.create(flux)
.expectNextMatches(i -> i == 2)
.expectNextMatches(i -> i == 4)
.expectNextMatches(i -> i == 6)
.expectNextMatches(i -> i == 8)
.expectNextMatches(i -> i == 10)
.verifyComplete();
}
@Test
void testError() {
Mono<String> mono = Mono.error(new IllegalArgumentException("参数错误"));
StepVerifier.create(mono)
.expectErrorMatches(e ->
e instanceof IllegalArgumentException &&
e.getMessage().equals("参数错误"))
.verify();
}
@Test
void testErrorType() {
Flux<Integer> flux = Flux.just(1, 2, 0)
.map(i -> 10 / i);
StepVerifier.create(flux)
.expectNext(10)
.expectNext(5)
.expectError(ArithmeticException.class)
.verify();
}
13.2 虚拟时间测试
java
@Test
void testIntervalWithVirtualTime() {
StepVerifier.withVirtualTime(() ->
Flux.interval(Duration.ofHours(1)).take(3))
.expectSubscription()
.expectNoEvent(Duration.ofHours(1))
.expectNext(0L)
.thenAwait(Duration.ofHours(1))
.expectNext(1L)
.thenAwait(Duration.ofHours(1))
.expectNext(2L)
.verifyComplete();
// 无需真正等待3小时
}
@Test
void testTimeoutWithVirtualTime() {
StepVerifier.withVirtualTime(() ->
Mono.delay(Duration.ofSeconds(30))
.timeout(Duration.ofSeconds(10)))
.expectSubscription()
.expectNoEvent(Duration.ofSeconds(10))
.expectError(TimeoutException.class)
.verify();
}
13.3 TestPublisher
java
@Test
void testWithTestPublisher() {
TestPublisher<String> publisher = TestPublisher.create();
Flux<String> flux = publisher.flux()
.map(String::toUpperCase)
.filter(s -> s.length() > 2);
StepVerifier.create(flux)
.then(() -> publisher.next("hi", "hello", "ok", "world"))
.expectNext("HELLO")
.expectNext("WORLD")
.then(() -> publisher.complete())
.verifyComplete();
}
十四、与 Spring WebFlux 集成
14.1 响应式 Controller
java
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
// 返回单个对象
@GetMapping("/{id}")
public Mono<ResponseEntity<User>> getUser(@PathVariable Long id) {
return userRepository.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
// 返回列表
@GetMapping
public Flux<User> listUsers(@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return userRepository.findAllBy(PageRequest.of(page, size));
}
// 创建资源
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Mono<User> createUser(@RequestBody Mono<UserCreateRequest> requestMono) {
return requestMono
.flatMap(request -> {
User user = new User(request.getName(), request.getEmail());
return userRepository.save(user);
});
}
// Server-Sent Events(SSE)
@GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(seq -> ServerSentEvent.<String>builder()
.id(String.valueOf(seq))
.event("heartbeat")
.data("tick-" + seq)
.build());
}
}
14.2 响应式 Repository(R2DBC)
java
// 实体类
@Table("users")
public class User {
@Id
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
// getters/setters...
}
// Repository接口
public interface UserRepository extends ReactiveCrudRepository<User, Long> {
Flux<User> findAllBy(Pageable pageable);
Mono<User> findByEmail(String email);
Flux<User> findByNameContaining(String keyword);
@Query("SELECT * FROM users WHERE created_at > :since")
Flux<User> findActiveSince(LocalDateTime since);
}
// Service层
@Service
public class UserService {
private final UserRepository userRepository;
public Mono<User> register(UserCreateRequest request) {
return userRepository.findByEmail(request.getEmail())
.flatMap(existing -> Mono.<User>error(
new DuplicateEmailException("邮箱已存在")))
.switchIfEmpty(Mono.defer(() -> {
User user = new User(request.getName(), request.getEmail());
return userRepository.save(user);
}));
}
}
14.3 WebClient(响应式HTTP客户端)
java
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(ExchangeFilterFunctions.basicAuthentication("user", "pass"))
.build();
}
}
@Service
public class ExternalApiService {
private final WebClient webClient;
public ExternalApiService(WebClient webClient) {
this.webClient = webClient;
}
// GET请求
public Mono<UserDTO> getUser(Long id) {
return webClient.get()
.uri("/api/users/{id}", id)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, response ->
Mono.error(new ResourceNotFoundException("用户不存在: " + id)))
.bodyToMono(UserDTO.class)
.timeout(Duration.ofSeconds(5))
.retryWhen(Retry.backoff(2, Duration.ofMillis(500)));
}
// POST请求
public Mono<OrderDTO> createOrder(OrderRequest request) {
return webClient.post()
.uri("/api/orders")
.bodyValue(request)
.retrieve()
.bodyToMono(OrderDTO.class);
}
// 并发请求聚合
public Mono<DashboardVO> getDashboard(Long userId) {
Mono<UserDTO> user = getUser(userId);
Mono<List<OrderDTO>> orders = getOrders(userId);
Mono<AccountDTO> account = getAccount(userId);
return Mono.zip(user, orders, account)
.map(tuple -> new DashboardVO(tuple.getT1(), tuple.getT2(), tuple.getT3()));
}
}
十五、调试
15.1 checkpoint
java
// 在操作链中添加检查点,异常时显示位置信息
Mono<User> result = getUserFromRemote()
.map(this::transformUser)
.checkpoint("远程用户获取后转换") // 异常栈中会显示此描述
.flatMap(this::enrichUser)
.checkpoint("用户信息丰富化")
.onErrorResume(e -> {
// 异常信息中会包含最近的checkpoint描述
log.error("处理失败", e);
return Mono.empty();
});
15.2 log(信号日志)
java
// 记录所有Reactor信号(订阅、请求、发射、完成、错误、取消)
Flux.range(1, 5)
.log("myFlux") // 记录所有信号
.map(i -> i * 2)
.subscribe();
// 输出示例:
// [myFlux] | onSubscribe([Synchronous Fuseable] FluxRange.RangeSubscription)
// [myFlux] | request(unbounded)
// [myFlux] | onNext(1)
// [myFlux] | onNext(2)
// ...
// [myFlux] | onComplete()
// 只记录特定信号
Flux.range(1, 5)
.log("filtered", Level.INFO, SignalType.ON_NEXT, SignalType.ON_ERROR)
.subscribe();
15.3 全局调试模式
java
// 开发环境:开启全局调试(性能开销大,不要在生产使用)
// 在main方法或配置类中:
Hooks.onOperatorDebug();
// 生产环境:使用ReactorDebugAgent(基于字节码增强,开销小)
// JVM参数: -javaagent:reactor-debug-agent.jar
// 或代码中:
ReactorDebugAgent.init();
15.4 常见陷阱与排查
java
// 陷阱1:忘记subscribe(流不会执行!)
Flux.just(1, 2, 3).map(i -> {
System.out.println("这行不会执行");
return i;
});
// 修复:必须调用subscribe()或返回给框架
// 陷阱2:在响应式链中阻塞
Flux.range(1, 10)
.map(i -> {
String result = blockingHttpCall(i); // 错误!阻塞了响应式线程
return result;
});
// 修复:使用subscribeOn(Schedulers.boundedElastic())包装
// 陷阱3:flatMap中吞掉错误
Flux.just(1, 2, 3)
.flatMap(i -> riskyOperation(i)
.onErrorReturn(null)); // 错误被静默吞掉
// 修复:至少记录日志 .doOnError(e -> log.warn(...))
// 陷阱4:在循环中创建订阅
for (int i = 0; i < 100; i++) {
service.getData(i).subscribe(); // 错误!创建了100个独立订阅
}
// 修复:使用Flux.range(0, 100).flatMap(i -> service.getData(i))
十六、最佳实践
16.1 设计原则
java
// 原则1:方法返回Mono/Flux,不要block
// 错误
public User getUser(Long id) {
return userRepository.findById(id).block(); // 阻塞!
}
// 正确
public Mono<User> getUser(Long id) {
return userRepository.findById(id);
}
// 原则2:使用defer延迟创建
// 错误:Mono.just在方法调用时就计算了
public Mono<String> getConfig() {
return Mono.just(loadConfigFromDisk()); // 立即执行I/O
}
// 正确:订阅时才执行
public Mono<String> getConfig() {
return Mono.fromCallable(this::loadConfigFromDisk)
.subscribeOn(Schedulers.boundedElastic());
}
// 原则3:避免过深的嵌套flatMap
// 错误:回调地狱
public Mono<Result> process(Input input) {
return step1(input).flatMap(a ->
step2(a).flatMap(b ->
step3(b).flatMap(c ->
step4(c).map(d -> new Result(a, b, c, d)))));
}
// 正确:使用zip或链式flatMap
public Mono<Result> process(Input input) {
return step1(input)
.flatMap(a -> step2(a).map(b -> Tuple.of(a, b)))
.flatMap(ab -> step3(ab.getT2()).map(c -> Tuple.of(ab, c)))
.map(abc -> new Result(abc.getT1().getT1(), abc.getT1().getT2(), abc.getT2()));
}
16.2 使用 transform 复用逻辑
java
// 定义可复用的操作符组合
public static <T> Function<Flux<T>, Flux<T>> withLogging(String name) {
return flux -> flux
.doOnSubscribe(s -> log.info("[{}] 开始", name))
.doOnNext(item -> log.debug("[{}] 元素: {}", name, item))
.doOnComplete(() -> log.info("[{}] 完成", name))
.doOnError(e -> log.error("[{}] 错误", name, e));
}
public static <T> Function<Mono<T>, Mono<T>> withRetryAndTimeout() {
return mono -> mono
.timeout(Duration.ofSeconds(5))
.retryWhen(Retry.backoff(3, Duration.ofMillis(500)));
}
// 使用
Flux<Order> orders = orderRepository.findAll()
.transform(withLogging("订单查询"))
.transform(withRetryAndTimeout());
16.3 性能优化
java
// 1. 避免不必要的线程切换
// 错误:多次publishOn
Flux.range(1, 100)
.publishOn(Schedulers.parallel())
.map(i -> i * 2)
.publishOn(Schedulers.parallel()) // 多余!
.map(i -> i + 1);
// 正确:一次切换
Flux.range(1, 100)
.publishOn(Schedulers.parallel())
.map(i -> i * 2)
.map(i -> i + 1);
// 2. 使用buffer批处理减少I/O次数
Flux<Event> events = eventSource.stream();
events.buffer(50) // 攒够50个
.flatMap(batch -> database.batchInsert(batch)) // 一次批量写入
.subscribe();
// 3. 并行处理CPU密集任务
Flux.range(1, 1000)
.parallel(8) // 分为8个rail
.runOn(Schedulers.parallel())
.map(i -> heavyComputation(i))
.sequential() // 合并回单Flux
.subscribe();
// 4. 使用cache避免重复计算
Mono<Config> config = loadExpensiveConfig()
.cache(Duration.ofMinutes(10)); // 10分钟内复用
16.4 资源管理
java
// 使用using操作符管理资源生命周期
Flux<String> readLines(String filePath) {
return Flux.using(
() -> new BufferedReader(new FileReader(filePath)), // 创建资源
reader -> Flux.generate( // 使用资源
() -> reader,
(r, sink) -> {
try {
String line = r.readLine();
if (line == null) sink.complete();
else sink.next(line);
} catch (IOException e) {
sink.error(e);
}
return r;
}
),
reader -> { // 清理资源
try { reader.close(); } catch (IOException ignored) {}
}
);
}
// doFinally确保清理
Mono<Connection> withConnection = getConnection()
.flatMap(conn -> executeQuery(conn).thenReturn(conn))
.doFinally(signal -> {
// 无论成功、失败、取消都会执行
releaseConnection();
});
16.5 代码规范总结
| 规则 | 说明 |
|---|---|
| 不要 block() | 在响应式链中永远不要调用 block() |
| 用 defer 包装 | 有副作用的创建用 defer/fromCallable |
| 显式错误处理 | 每个外部调用都应有错误处理策略 |
| 合理选择调度器 | CPU密集用parallel,I/O用boundedElastic |
| 避免嵌套 | 超过3层flatMap考虑重构 |
| 使用transform | 复用操作符组合 |
| 测试用StepVerifier | 不要依赖block()做测试 |
| 日志用doOn系列 | 不要在map中打日志(副作用) |
| Context传上下文 | 不要用ThreadLocal |
| 注意订阅 | 确保流最终被subscribe |
