Java深入解析篇十八之响应式编程
响应式编程概述与Reactive Manifesto
什么是响应式编程
响应式编程(Reactive Programming)是一种面向数据流和变化传播的编程范式。它关注的是数据流的构建、转换和组合,而非传统的命令式控制流。
核心思想:
- 数据是"流"(Stream),而非离散值
- 通过声明式操作符组合转换数据流
- 异步非阻塞地处理数据
- 通过背压机制协调生产者和消费者速率
Reactive Manifesto(响应式宣言)
2014年发布,定义了响应式系统的四大特征:
| 特征 | 含义 | 实现手段 |
|---|---|---|
| 响应性 (Responsive) | 快速、一致地响应用户 | 异步处理、非阻塞IO |
| 弹性 (Resilient) | 面对故障仍能保持响应 | 故障隔离、复制、委托 |
| 伸缩性 (Elastic) | 负载变化时自动伸缩 | 无共享状态、消息驱动 |
| 消息驱动 (Message Driven) | 组件间通过异步消息通信 | 非阻塞、背压、位置透明 |
四者关系:消息驱动是基础,支撑弹性、伸缩性,最终实现响应性。
java
// 响应式编程的核心体验:声明式数据流处理
// 传统命令式
List<String> result = new ArrayList<>();
for (User user : users) {
if (user.getAge() > 18) {
result.add(user.getName().toUpperCase());
}
}
// 响应式(Reactor风格)
Flux.fromIterable(users)
.filter(user -> user.getAge() > 18)
.map(user -> user.getName().toUpperCase())
.subscribe(name -> System.out.println(name));
同步vs异步、阻塞vs非阻塞
同步与异步
同步(Synchronous):调用者发出请求后,必须等待结果返回才能继续执行。
异步(Asynchronous):调用者发出请求后立即返回,结果通过回调、通知或轮询获取。
java
// 同步调用:线程阻塞等待结果
String result = httpClient.get("http://api.example.com/data");
System.out.println(result); // 必须等上面完成
// 异步调用:立即返回,结果通过回调获取
httpClient.getAsync("http://api.example.com/data")
.thenAccept(result -> System.out.println(result));
System.out.println("这行会先执行");
阻塞与非阻塞
阻塞(Blocking):调用方线程在等待IO完成期间被挂起,不能做其他事情。
非阻塞(Non-blocking):调用方线程不会被挂起,IO操作立即返回(可能返回不完整结果),通过事件通知完成。
java
// 阻塞IO(BIO):线程在read时挂起
InputStream in = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer); // 线程阻塞直到有数据
// 非阻塞IO(NIO):线程不挂起
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buf); // 立即返回,可能为0
// 通过Selector监听可读事件
四种组合
| 同步 | 异步 | |
|---|---|---|
| 阻塞 | 传统BIO:线程等待IO完成 | 少见(异步+阻塞矛盾) |
| 非阻塞 | 轮询:不断检查是否就绪 | NIO/Reactive:事件通知+非阻塞 |
java
// 同步阻塞:最传统的方式
public String fetchData() throws IOException {
URL url = new URL("http://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
return reader.lines().collect(Collectors.joining());
}
// 线程从头阻塞到尾
}
// 异步非阻塞:响应式方式
public Mono<String> fetchDataReactive() {
return WebClient.create()
.get()
.uri("http://api.example.com/data")
.retrieve()
.bodyToMono(String.class);
// 立即返回Mono,不阻塞任何线程
}
推模型vs拉模型
拉模型(Pull-based)
消费者主动从数据源拉取数据,消费者控制消费速率。
java
// 典型的拉模型:Iterator
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) { // 消费者主动询问
String item = iterator.next(); // 消费者主动拉取
process(item);
}
// JDBC ResultSet也是拉模型
ResultSet rs = statement.executeQuery(sql);
while (rs.next()) { // 消费者逐行拉取
String name = rs.getString("name");
}
特点:
- 消费者控制速率,天然不会过载
- 消费者需要轮询,可能有延迟
- 无法感知数据何时就绪
推模型(Push-based)
生产者主动将数据推送给消费者,生产者控制推送速率。
java
// 典型的推模型:Observer模式
public interface Observer<T> {
void onNext(T item);
void onError(Throwable t);
void onComplete();
}
// 生产者主动推送
public class DataProducer {
private List<Observer<String>> observers = new ArrayList<>();
public void addObserver(Observer<String> observer) {
observers.add(observer);
}
public void produce(String data) {
// 生产者主动推送给所有观察者
for (Observer<String> observer : observers) {
observer.onNext(data);
}
}
}
特点:
- 实时性好,数据到达立即通知
- 生产者控制速率,可能压垮消费者
- 需要背压机制协调速率
推+拉结合:Reactive Streams
Reactive Streams的创新在于将推模型和拉模型结合:
- 数据通过推模型传递(onNext)
- 速率通过拉模型控制(request(n))
java
// Reactive Streams = 推(数据)+ 拉(请求量)
// Subscriber通过request(n)告诉Publisher:"我准备好接收n个数据了"
// Publisher收到request后,通过onNext推送最多n个数据
// 这样既保证了实时性,又避免了过载
Reactive Streams规范
规范背景
Reactive Streams是2013年底由Netflix、Pivotal、Lightbend、Twitter等公司共同发起的规范,目标是提供异步流处理的互操作标准。2017年,该规范被纳入JDK 9的java.util.concurrent.Flow类中。
四个核心接口
java
// 完整的Reactive Streams规范(仅4个接口)
package org.reactivestreams;
// 1. 发布者:数据的生产者
public interface Publisher<T> {
void subscribe(Subscriber<? super T> subscriber);
}
// 2. 订阅者:数据的消费者
public interface Subscriber<T> {
void onSubscribe(Subscription subscription);
void onNext(T item);
void onError(Throwable throwable);
void onComplete();
}
// 3. 订阅关系:连接Publisher和Subscriber的桥梁
public interface Subscription {
void request(long n); // 请求n个元素
void cancel(); // 取消订阅
}
// 4. 处理器:既是Subscriber又是Publisher
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
规范核心规则
Publisher规则:
subscribe()必须调用Subscriber的onSubscribe()- 发出的onNext数量不能超过已request的数量
- 必须以onError或onComplete终止
- 终止后不能再发送任何信号
Subscriber规则:
- 必须在
onSubscribe()中调用request()才能收到数据 - 必须准备好接收onNext(不能抛异常)
- onError/onComplete后不需要再处理任何信号
- 信号必须串行处理(不能并发调用onNext)
Subscription规则:
request(n)中n必须>0request()和cancel()必须线程安全cancel()是幂等的
java
// 完整示例:手动实现Publisher和Subscriber
import org.reactivestreams.*;
// 自定义Publisher:发射指定范围的整数
public class RangePublisher implements Publisher<Integer> {
private final int start;
private final int count;
public RangePublisher(int start, int count) {
this.start = start;
this.count = count;
}
@Override
public void subscribe(Subscriber<? super Integer> subscriber) {
subscriber.onSubscribe(new RangeSubscription(subscriber, start, count));
}
// 内部Subscription实现
private static class RangeSubscription implements Subscription {
private final Subscriber<? super Integer> subscriber;
private int current;
private int remaining;
private volatile boolean cancelled = false;
RangeSubscription(Subscriber<? super Integer> subscriber, int start, int count) {
this.subscriber = subscriber;
this.current = start;
this.remaining = count;
}
@Override
public void request(long n) {
if (n <= 0) {
subscriber.onError(new IllegalArgumentException("n must be > 0"));
return;
}
if (cancelled) return;
// 按需发射数据(背压的核心)
long emitted = 0;
while (emitted < n && remaining > 0 && !cancelled) {
subscriber.onNext(current++);
remaining--;
emitted++;
}
if (remaining == 0 && !cancelled) {
subscriber.onComplete();
}
}
@Override
public void cancel() {
cancelled = true;
}
}
}
// 自定义Subscriber:打印接收到的数据
public class PrintSubscriber implements Subscriber<Integer> {
private Subscription subscription;
private final int batchSize;
public PrintSubscriber(int batchSize) {
this.batchSize = batchSize;
}
@Override
public void onSubscribe(Subscription subscription) {
this.subscription = subscription;
System.out.println("已订阅,请求第一批数据: " + batchSize);
subscription.request(batchSize); // 必须request才能收到数据
}
@Override
public void onNext(Integer item) {
System.out.println("收到: " + item);
// 每收到batchSize个数据,再请求下一批
if (item % batchSize == 0) {
subscription.request(batchSize);
}
}
@Override
public void onError(Throwable throwable) {
System.err.println("错误: " + throwable.getMessage());
}
@Override
public void onComplete() {
System.out.println("数据流结束");
}
}
// 使用
public class ReactiveStreamsDemo {
public static void main(String[] args) {
RangePublisher publisher = new RangePublisher(1, 100);
PrintSubscriber subscriber = new PrintSubscriber(10);
publisher.subscribe(subscriber);
// 输出:每次请求10个,分批接收,直到100个全部完成
}
}
Publisher/Subscriber/Subscription/Processor详解
Publisher(发布者)
Publisher是数据流的源头,负责生产数据并响应订阅请求。
java
// Publisher的核心职责
// 1. 接受订阅
// 2. 响应request(n)发射数据
// 3. 在适当时机发送终止信号
// 使用Reactor创建各种Publisher
Flux<Integer> rangeFlux = Flux.range(1, 10); // 发射1-10
Flux<String> justFlux = Flux.just("A", "B", "C"); // 发射固定元素
Mono<String> mono = Mono.just("single value"); // 0或1个元素
Flux<Long> interval = Flux.interval(Duration.ofSeconds(1)); // 每秒发射
Flux<String> fromStream = Flux.fromStream( // 从Java Stream
Stream.of("x", "y", "z")
);
Flux<Integer> generate = Flux.generate( // 程序化生成
() -> 0,
(state, sink) -> {
sink.next(state);
if (state == 10) sink.complete();
return state + 1;
}
);
Subscriber(订阅者)
Subscriber是数据流的消费者,通过回调方法接收数据。
java
// Reactor中简化的订阅方式(Lambda形式)
Flux.range(1, 5)
.subscribe(
item -> System.out.println("onNext: " + item), // 消费数据
error -> System.err.println("onError: " + error), // 处理错误
() -> System.out.println("onComplete") // 完成回调
);
// 带Subscription控制的订阅
Flux.interval(Duration.ofMillis(100))
.subscribe(new BaseSubscriber<Long>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
System.out.println("订阅建立");
request(5); // 初始请求5个
}
@Override
protected void hookOnNext(Long value) {
System.out.println("收到: " + value);
if (value % 5 == 0) {
request(5); // 每5个再请求5个
}
}
@Override
protected void hookOnComplete() {
System.out.println("完成");
}
@Override
protected void hookOnError(Throwable throwable) {
System.err.println("错误: " + throwable);
}
});
Subscription(订阅关系)
Subscription是Publisher和Subscriber之间的控制通道。
java
// Subscription的两个核心方法
// request(long n):向上游请求n个元素(背压的核心)
// cancel():取消订阅,停止接收数据
// 实际应用:限时订阅(超时自动取消)
Flux.interval(Duration.ofMillis(100))
.take(Duration.ofSeconds(2)) // 2秒后自动cancel
.subscribe(tick -> System.out.println("tick: " + tick));
// 手动控制订阅生命周期
Disposable disposable = Flux.interval(Duration.ofSeconds(1))
.subscribe(tick -> System.out.println("tick: " + tick));
// 在适当时机取消
// disposable.dispose(); // 相当于Subscription.cancel()
Processor(处理器)
Processor同时实现Publisher和Subscriber,是数据流的中间转换节点。
java
// Processor = Subscriber(接收上游数据)+ Publisher(向下游发射数据)
// 在Reactor中,操作符(map/filter/flatMap)本质上就是Processor
// 手动实现一个简单的Processor:将Integer转为String
public class IntToStringProcessor implements Processor<Integer, String> {
private Subscriber<? super String> downstream;
private Subscription upstreamSubscription;
@Override
public void subscribe(Subscriber<? super String> subscriber) {
this.downstream = subscriber;
}
@Override
public void onSubscribe(Subscription subscription) {
this.upstreamSubscription = subscription;
if (downstream != null) {
downstream.onSubscribe(new Subscription() {
@Override
public void request(long n) {
upstreamSubscription.request(n); // 透传request
}
@Override
public void cancel() {
upstreamSubscription.cancel();
}
});
}
}
@Override
public void onNext(Integer item) {
if (downstream != null) {
downstream.onNext("Number: " + item); // 转换数据
}
}
@Override
public void onError(Throwable throwable) {
if (downstream != null) downstream.onError(throwable);
}
@Override
public void onComplete() {
if (downstream != null) downstream.onComplete();
}
}
背压(Backpressure)机制
什么是背压
背压是响应式流的核心机制,解决"快生产者、慢消费者"问题。消费者通过request(n)告诉生产者自己能处理的速率,生产者据此调整发射速度。
背压策略
java
// Reactor中的背压策略演示
// 1. BUFFER(缓冲):将多余数据缓存在内存中
Flux.interval(Duration.ofMillis(1)) // 快生产者
.onBackpressureBuffer(100) // 最多缓冲100个
.publishOn(Schedulers.parallel())
.subscribe(new BaseSubscriber<Long>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(10); // 慢消费者,每次只要10个
}
@Override
protected void hookOnNext(Long value) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("处理: " + value);
request(1);
}
});
// 2. DROP(丢弃):丢弃处理不了的数据
Flux.interval(Duration.ofMillis(1))
.onBackpressureDrop(dropped ->
System.out.println("丢弃: " + dropped))
.subscribe(slowConsumer);
// 3. LATEST(只保留最新):只保留最新的一个数据
Flux.interval(Duration.ofMillis(1))
.onBackpressureLatest()
.subscribe(slowConsumer);
// 4. ERROR(报错):超出容量时发出错误信号
Flux.interval(Duration.ofMillis(1))
.onBackpressureError()
.subscribe(slowConsumer);
背压的实际意义
java
// 场景:数据库批量读取
// 没有背压:一次性加载所有数据到内存 -> OOM
// 有背压:按需加载,每次只取一批
// 使用Reactor的背压实现分批处理
Flux.range(0, 1000000)
.buffer(1000) // 每1000个一批
.flatMap(batch -> {
// 每批写入数据库
return Mono.fromRunnable(() -> {
System.out.println("写入一批: " + batch.size() + "条");
// batchInsert(batch);
}).subscribeOn(Schedulers.boundedElastic());
})
.blockLast(); // 等待全部完成
// 文件流式读取(背压控制读取速度)
Flux.using(
() -> Files.lines(Path.of("large-file.txt")), // 资源创建
flux -> Flux.fromStream(flux), // 使用资源
stream -> stream.close() // 清理资源
)
.map(line -> processLine(line))
.subscribe(result -> saveResult(result));
Flow API(JDK 9 java.util.concurrent.Flow)
Flow类结构
JDK 9将Reactive Streams规范纳入标准库:
java
// java.util.concurrent.Flow 包含4个内部接口
// Flow.Publisher<T> 等同于 org.reactivestreams.Publisher<T>
// Flow.Subscriber<T> 等同于 org.reactivestreams.Subscriber<T>
// Flow.Subscription 等同于 org.reactivestreams.Subscription
// Flow.Processor<T,R> 等同于 org.reactivestreams.Processor<T,R>
// JDK还提供了一个内置实现:SubmissionPublisher<T>
SubmissionPublisher使用示例
java
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.TimeUnit;
public class FlowApiDemo {
// 自定义Subscriber
static class MySubscriber implements Flow.Subscriber<String> {
private Flow.Subscription subscription;
private final String name;
MySubscriber(String name) {
this.name = name;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
System.out.println(name + " 已订阅");
subscription.request(1); // 请求第一个数据
}
@Override
public void onNext(String item) {
System.out.println(name + " 收到: " + item);
subscription.request(1); // 处理完一个再请求下一个
}
@Override
public void onError(Throwable throwable) {
System.err.println(name + " 错误: " + throwable.getMessage());
}
@Override
public void onComplete() {
System.out.println(name + " 完成");
}
}
public static void main(String[] args) throws InterruptedException {
// 创建SubmissionPublisher(基于ForkJoinPool异步分发)
SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
// 添加订阅者
publisher.subscribe(new MySubscriber("订阅者A"));
publisher.subscribe(new MySubscriber("订阅者B"));
// 发布数据
for (int i = 1; i <= 5; i++) {
publisher.submit("消息-" + i);
Thread.sleep(100);
}
// 关闭发布者(发送onComplete)
publisher.close();
// 等待异步处理完成
TimeUnit.SECONDS.sleep(2);
}
}
Flow API的局限性
java
// Flow API只提供最小规范,缺少操作符
// 无法直接做:map / filter / flatMap / merge / zip 等
// 对比Reactor的丰富操作符:
Flux.range(1, 10)
.filter(n -> n % 2 == 0) // Flow API没有
.map(n -> n * 10) // Flow API没有
.flatMap(n -> queryAsync(n)) // Flow API没有
.take(3) // Flow API没有
.subscribe(System.out::println);
// 生产环境中,通常使用Reactor或RxJava
// Flow API的价值在于:提供互操作标准
// 不同响应式库之间可以通过Flow接口互操作
响应式编程 vs 传统命令式编程
编程范式对比
java
// === 场景:查询用户订单并计算总金额 ===
// 传统命令式(同步阻塞)
public BigDecimal getOrderTotal(Long userId) {
User user = userRepository.findById(userId); // 阻塞等待
List<Order> orders = orderRepository.findByUser(user); // 阻塞等待
BigDecimal total = BigDecimal.ZERO;
for (Order order : orders) {
for (OrderItem item : order.getItems()) {
total = total.add(item.getPrice()
.multiply(BigDecimal.valueOf(item.getQuantity())));
}
}
return total;
}
// 响应式(异步非阻塞)
public Mono<BigDecimal> getOrderTotalReactive(Long userId) {
return userRepository.findById(userId) // 返回Mono<User>
.flatMap(user -> orderRepository.findByUser(user)) // 返回Flux<Order>
.flatMap(order -> Flux.fromIterable(order.getItems()))
.map(item -> item.getPrice()
.multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add); // 聚合
}
错误处理对比
java
// 命令式:try-catch嵌套
public String processOrder(Long orderId) {
try {
Order order = orderRepository.findById(orderId);
try {
Payment payment = paymentService.charge(order);
try {
notificationService.send(order.getUser(), payment);
return "success";
} catch (NotificationException e) {
log.warn("通知失败", e);
return "partial";
}
} catch (PaymentException e) {
return "payment_failed";
}
} catch (OrderNotFoundException e) {
return "not_found";
}
}
// 响应式:操作符链式错误处理
public Mono<String> processOrderReactive(Long orderId) {
return orderRepository.findById(orderId)
.switchIfEmpty(Mono.error(new OrderNotFoundException(orderId)))
.flatMap(order -> paymentService.charge(order)
.flatMap(payment -> notificationService.send(order.getUser(), payment)
.thenReturn("success")
.onErrorReturn(NotificationException.class, "partial")
)
.onErrorReturn(PaymentException.class, "payment_failed")
)
.onErrorReturn(OrderNotFoundException.class, "not_found");
}
并发处理对比
java
// 命令式:并行查询多个服务
public UserProfile getUserProfile(Long userId) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<User> userFuture = executor.submit(() -> userService.get(userId));
Future<List<Order>> ordersFuture = executor.submit(() -> orderService.get(userId));
Future<Account> accountFuture = executor.submit(() -> accountService.get(userId));
// 阻塞等待所有结果
User user = userFuture.get(5, TimeUnit.SECONDS);
List<Order> orders = ordersFuture.get(5, TimeUnit.SECONDS);
Account account = accountFuture.get(5, TimeUnit.SECONDS);
executor.shutdown();
return new UserProfile(user, orders, account);
}
// 响应式:并行查询(无阻塞)
public Mono<UserProfile> getUserProfileReactive(Long userId) {
Mono<User> userMono = userService.get(userId);
Mono<List<Order>> ordersMono = orderService.get(userId);
Mono<Account> accountMono = accountService.get(userId);
return Mono.zip(userMono, ordersMono, accountMono)
.map(tuple -> new UserProfile(
tuple.getT1(), tuple.getT2(), tuple.getT3()))
.timeout(Duration.ofSeconds(5));
}
响应式系统特征
弹性(Resilience)
系统面对故障仍能保持响应,通过隔离和委托实现。
java
// 使用Reactor实现弹性:超时 + 重试 + 降级
public Mono<Product> getProductResilient(Long productId) {
return webClient.get()
.uri("/products/{id}", productId)
.retrieve()
.bodyToMono(Product.class)
.timeout(Duration.ofSeconds(3)) // 超时保护
.retryWhen(Retry.backoff(3, Duration.ofMillis(500)) // 指数退避重试
.filter(ex -> ex instanceof TimeoutException))
.onErrorResume(ex -> { // 降级:返回缓存
System.out.println("降级到缓存: " + ex.getMessage());
return Mono.just(Product.cached(productId));
});
}
伸缩性(Elastic)
系统在负载变化时自动伸缩,无单点瓶颈。
java
// 响应式系统天然支持伸缩:
// 1. 无共享状态 -> 水平扩展无锁竞争
// 2. 消息驱动 -> 组件间松耦合
// 3. 背压 -> 过载时自动降速而非崩溃
// 使用flatMap的concurrency参数控制并发度
Flux.fromIterable(requestIds)
.flatMap(id -> callRemoteService(id), 50) // 最多50个并发
.subscribe(result -> processResult(result));
// 动态并发控制
Flux.fromIterable(tasks)
.flatMap(task -> {
int concurrency = Runtime.getRuntime().availableProcessors();
return processTask(task).subscribeOn(Schedulers.parallel());
}, Runtime.getRuntime().availableProcessors())
.subscribe();
消息驱动(Message Driven)
组件间通过异步消息通信,实现松耦合和位置透明。
java
// 消息驱动的响应式系统示例
// 组件A发送消息,不关心组件B在哪里、何时处理
// 事件总线模式
public class ReactiveEventBus {
private final Map<Class<?>, FluxSink<Object>> sinks = new ConcurrentHashMap<>();
public <T> Flux<T> on(Class<T> eventType) {
return Flux.create(sink -> {
sinks.put(eventType, (FluxSink<Object>) sink);
});
}
public <T> void publish(T event) {
FluxSink<Object> sink = sinks.get(event.getClass());
if (sink != null) {
sink.next(event); // 异步推送,不阻塞发布者
}
}
}
// 使用
ReactiveEventBus bus = new ReactiveEventBus();
bus.on(OrderCreatedEvent.class)
.subscribe(event -> sendNotification(event));
bus.on(OrderCreatedEvent.class)
.subscribe(event -> updateInventory(event));
// 发布事件(不关心谁在监听)
bus.publish(new OrderCreatedEvent(orderId, userId));
主流响应式框架对比
Project Reactor
java
// Reactor是Spring生态的响应式基础
// 核心类型:Mono(0-1个元素)和 Flux(0-N个元素)
// Mono示例
Mono<String> mono = Mono.just("hello")
.map(String::toUpperCase)
.flatMap(s -> Mono.just(s + " WORLD"))
.defaultIfEmpty("fallback");
// Flux示例
Flux<Integer> flux = Flux.range(1, 100)
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.take(10)
.collectList(); // Flux -> Mono<List>
// 调度器
Flux.range(1, 10)
.publishOn(Schedulers.parallel()) // 下游在parallel线程执行
.subscribeOn(Schedulers.boundedElastic()) // 订阅在elastic线程执行
.subscribe(n -> System.out.println(
Thread.currentThread().getName() + ": " + n));
RxJava
java
// RxJava 3.x 核心类型
// Flowable:支持背压(对应Reactor的Flux)
// Observable:不支持背压
// Single:1个元素(对应Mono)
// Maybe:0或1个元素
// Completable:无元素,只关心完成/错误
// Flowable(支持背压)
Flowable.range(1, 1000)
.onBackpressureBuffer()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribe(new DisposableSubscriber<Integer>() {
@Override
public void onNext(Integer item) {
System.out.println("收到: " + item);
request(10); // 背压:每次请求10个
}
@Override
public void onError(Throwable t) {
t.printStackTrace();
}
@Override
public void onComplete() {
System.out.println("完成");
}
});
// Observable(不支持背压,适合UI事件等不会过载的场景)
Observable.interval(1, TimeUnit.SECONDS)
.map(tick -> "tick-" + tick)
.take(5)
.subscribe(System.out::println);
// Single
Single.just("hello")
.map(String::toUpperCase)
.subscribe(result -> System.out.println(result));
Akka Streams
java
// Akka Streams基于Actor模型,使用Graph DSL
// 核心概念:Source(源)、Flow(流)、Sink(汇)
// Java DSL示例(需要akka-stream依赖)
import akka.actor.ActorSystem;
import akka.stream.javadsl.*;
ActorSystem system = ActorSystem.create("reactive-system");
// 构建流:Source -> Flow -> Sink
Source<Integer, NotUsed> source = Source.range(1, 100);
Flow<Integer, Integer, NotUsed> filterFlow =
Flow.of(Integer.class).filter(n -> n % 2 == 0);
Flow<Integer, String, NotUsed> mapFlow =
Flow.of(Integer.class).map(n -> "num-" + n);
Sink<String, CompletionStage<Done>> sink =
Sink.foreach(s -> System.out.println(s));
// 组合并运行
RunnableGraph<CompletionStage<Done>> graph =
source.via(filterFlow).via(mapFlow).toMat(sink, Keep.right());
CompletionStage<Done> result = graph.run(system);
框架对比表
| 维度 | Reactor | RxJava | Akka Streams |
|---|---|---|---|
| 背压 | 完整支持 | Flowable支持 | 内置支持 |
| 核心类型 | Mono/Flux | Flowable/Observable/Single | Source/Flow/Sink |
| 生态 | Spring WebFlux | Android/通用 | 分布式系统 |
| 操作符 | 丰富 | 最丰富 | 中等 |
| 学习曲线 | 中等 | 中等 | 较陡 |
| 调试支持 | Hooks.onOperatorDebug | RxJavaPlugins | 日志级别 |
| 线程模型 | Scheduler | Scheduler | Actor/Dispatcher |
响应式编程适用场景
高并发IO密集型
java
// 场景:API网关,需要聚合多个微服务的响应
// 传统方式:每个请求占用一个线程等待 -> 线程池耗尽
// 响应式:少量线程处理大量并发请求
// Spring WebFlux网关聚合
@GetMapping("/dashboard/{userId}")
public Mono<Dashboard> getDashboard(@PathVariable Long userId) {
Mono<UserInfo> userMono = userService.getUser(userId);
Mono<List<Order>> ordersMono = orderService.getRecentOrders(userId);
Mono<List<Notification>> notifsMono = notifService.getUnread(userId);
Mono<AccountBalance> balanceMono = accountService.getBalance(userId);
// 4个服务并行调用,总耗时 = 最慢的那个(而非4个之和)
return Mono.zip(userMono, ordersMono, notifsMono, balanceMono)
.map(t -> new Dashboard(t.getT1(), t.getT2(), t.getT3(), t.getT4()));
}
流式数据处理
java
// 场景:实时日志处理管道
// 读取 -> 过滤 -> 解析 -> 富化 -> 写入
Flux<String> logStream = Flux.create(sink -> {
// 模拟日志源(实际可能是Kafka/Tail文件)
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
sink.next(generateLogLine());
}, 0, 10, TimeUnit.MILLISECONDS);
});
logStream
.filter(line -> line.contains("ERROR")) // 过滤错误日志
.map(this::parseLogEntry) // 解析为结构化对象
.flatMap(entry -> enrichWithMetadata(entry)) // 异步富化
.buffer(Duration.ofSeconds(5)) // 5秒一批
.flatMap(batch -> writeToElasticsearch(batch)) // 批量写入
.subscribe();
实时推送系统
java
// 场景:股票行情实时推送(SSE - Server-Sent Events)
@GetMapping(value = "/stocks/{symbol}/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<StockQuote> streamQuotes(@PathVariable String symbol) {
return Flux.interval(Duration.ofMillis(500))
.map(tick -> stockService.getLatestQuote(symbol))
.distinctUntilChanged(StockQuote::getPrice) // 价格变化时才推送
.timeout(Duration.ofMinutes(30)) // 30分钟无活动断开
.doOnCancel(() -> log.info("客户端断开: {}", symbol));
}
不适合的场景
java
// 1. CPU密集型计算:响应式无法加速计算本身
// 错误用法:
Mono.fromCallable(() -> computePrimeNumbers(1000000)) // CPU密集
.subscribeOn(Schedulers.parallel())
.subscribe();
// 直接用并行计算或虚拟线程更合适
// 2. 简单CRUD:响应式增加复杂度但无性能收益
// 错误用法:
Mono.just(userId)
.flatMap(id -> userRepository.findById(id))
.flatMap(user -> userRepository.save(user))
.subscribe();
// 简单的同步调用更清晰
// 3. 团队不熟悉:维护成本高于收益
// 响应式代码的调试、测试、问题排查都需要专门知识
响应式编程陷阱与反模式
陷阱1:阻塞响应式线程
java
// 反模式:在响应式链中调用阻塞方法
Flux.range(1, 10)
.map(n -> {
// 严重错误!阻塞了事件循环线程
Thread.sleep(1000); // 阻塞!
return httpClient.getBlocking("/api/" + n); // 阻塞IO!
})
.subscribe();
// 正确做法:使用subscribeOn切换到弹性线程池
Flux.range(1, 10)
.flatMap(n ->
Mono.fromCallable(() -> httpClient.getBlocking("/api/" + n))
.subscribeOn(Schedulers.boundedElastic()) // 阻塞操作在elastic线程
)
.subscribe();
// 更好的做法:使用非阻塞客户端
Flux.range(1, 10)
.flatMap(n -> webClient.get().uri("/api/" + n)
.retrieve().bodyToMono(String.class))
.subscribe();
陷阱2:忽略错误处理
java
// 反模式:错误被静默吞掉
Flux.range(1, 10)
.map(n -> 100 / (n - 5)) // n=5时除零异常
.subscribe(n -> System.out.println(n));
// 错误被默认handler打印到stderr,容易被忽略
// 正确做法:显式处理错误
Flux.range(1, 10)
.map(n -> 100 / (n - 5))
.onErrorContinue((ex, obj) ->
log.warn("跳过元素 {}: {}", obj, ex.getMessage()))
.subscribe(
n -> System.out.println(n),
error -> log.error("流处理失败", error)
);
陷阱3:无限缓冲导致OOM
java
// 反模式:无界缓冲
Flux.interval(Duration.ofMillis(1)) // 极快的生产者
.onBackpressureBuffer() // 无界缓冲!内存会持续增长
.publishOn(Schedulers.single())
.subscribe(slowConsumer);
// 正确做法:有界缓冲 + 溢出策略
Flux.interval(Duration.ofMillis(1))
.onBackpressureBuffer(
1000, // 最多缓冲1000个
dropped -> log.warn("丢弃: {}", dropped), // 溢出回调
BufferOverflowStrategy.DROP_OLDEST // 丢弃最旧的
)
.publishOn(Schedulers.single())
.subscribe(slowConsumer);
陷阱4:调试困难
java
// 响应式堆栈通常不包含完整的调用链
// 解决方案1:开启调试模式(开发环境)
Hooks.onOperatorDebug(); // 全局开启,有性能开销
// 解决方案2:使用checkpoint标记位置
Flux.range(1, 10)
.map(n -> n * 2)
.checkpoint("map之后") // 在堆栈中标记
.filter(n -> n > 5)
.checkpoint("filter之后")
.subscribe();
// 解决方案3:Reactor 3.4+ 使用 Micrometer 追踪
// 添加reactor-core-micrometer依赖
Hooks.enableAutomaticContextPropagation();
陷阱5:未取消的订阅(内存泄漏)
java
// 反模式:创建了订阅但从未取消
public class BadService {
public void start() {
// 这个订阅永远不会被取消 -> 内存泄漏
Flux.interval(Duration.ofSeconds(1))
.subscribe(tick -> doSomething());
}
}
// 正确做法:管理订阅生命周期
public class GoodService implements DisposableBean {
private final Disposable subscription;
public GoodService() {
this.subscription = Flux.interval(Duration.ofSeconds(1))
.subscribe(tick -> doSomething());
}
@Override
public void destroy() {
subscription.dispose(); // 组件销毁时取消订阅
}
}
与虚拟线程的关系和对比
虚拟线程简介(JDK 21+)
java
// 虚拟线程:轻量级线程,由JVM调度而非OS
// 创建成本极低(~几百字节栈),可以创建数百万个
// 创建虚拟线程
Thread.startVirtualThread(() -> {
System.out.println("Hello from virtual thread");
});
// 使用Executor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 100_000; i++) {
executor.submit(() -> {
// 每个任务一个虚拟线程,阻塞也不心疼
String result = blockingHttpCall();
return result;
});
}
}
核心对比
| 维度 | 响应式编程 | 虚拟线程 |
|---|---|---|
| 并发模型 | 少量线程 + 事件循环 | 大量虚拟线程 + 阻塞 |
| API风格 | 链式操作符(Mono/Flux) | 传统同步风格 |
| 学习曲线 | 陡峭 | 平缓(和同步代码一样) |
| 调试 | 困难(堆栈不完整) | 容易(完整堆栈) |
| 背压 | 内置支持 | 无内置支持 |
| 流处理 | 天然适合 | 需要额外库 |
| 线程阻塞 | 禁止(会破坏事件循环) | 允许(核心设计) |
| 适用JDK | 8+(通过库) | 21+ |
代码对比
java
// 同一个需求:并发调用3个服务并聚合结果
// 响应式方式
public Mono<Result> aggregateReactive(String id) {
return Mono.zip(
serviceA.call(id), // Mono<A>
serviceB.call(id), // Mono<B>
serviceC.call(id) // Mono<C>
).map(tuple -> new Result(tuple.getT1(), tuple.getT2(), tuple.getT3()));
}
// 虚拟线程方式(代码和同步一样直观)
public Result aggregateVirtualThread(String id) throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<A> fa = executor.submit(() -> serviceA.callBlocking(id));
Future<B> fb = executor.submit(() -> serviceB.callBlocking(id));
Future<C> fc = executor.submit(() -> serviceC.callBlocking(id));
return new Result(fa.get(), fb.get(), fc.get());
}
}
互补而非替代
java
// 虚拟线程不能替代响应式的场景:
// 1. 背压控制:虚拟线程没有内置背压
// 快生产者 + 慢消费者 -> 仍需响应式方案
Flux.from(kafkaConsumer)
.onBackpressureBuffer(1000)
.flatMap(msg -> processAsync(msg), 50) // 控制并发度
.subscribe();
// 2. 流式处理:数据流的组合、转换、窗口
Flux.interval(Duration.ofSeconds(1))
.window(Duration.ofSeconds(10)) // 10秒一个窗口
.flatMap(window -> window.reduce(0L, Long::sum))
.subscribe(sum -> report(sum));
// 3. 事件驱动架构:发布/订阅、事件组合
Mono<UserCreatedEvent> userCreated = eventBus.on(UserCreatedEvent.class);
Mono<EmailVerifiedEvent> emailVerified = eventBus.on(EmailVerifiedEvent.class);
Mono.zip(userCreated, emailVerified)
.subscribe(tuple -> activateAccount(tuple.getT1(), tuple.getT2()));
// 虚拟线程更适合的场景:
// - 简单的并发IO调用
// - 迁移遗留阻塞代码
// - 团队不熟悉响应式时的替代方案
最佳实践
1. 永远不要阻塞响应式线程
java
// 规则:在parallel/single调度器上,绝不调用阻塞方法
// 阻塞操作必须切换到boundedElastic
// 错误
Mono.fromCallable(() -> jdbcTemplate.queryForObject(sql, Long.class))
.subscribe(); // 在默认线程上执行阻塞JDBC -> 灾难
// 正确
Mono.fromCallable(() -> jdbcTemplate.queryForObject(sql, Long.class))
.subscribeOn(Schedulers.boundedElastic()) // 切换到弹性线程池
.subscribe();
// 最佳:使用响应式驱动(R2DBC替代JDBC)
r2dbcTemplate.selectOne(query, User.class)
.subscribe(user -> process(user));
2. 合理使用调度器
java
// Schedulers.parallel():CPU密集,线程数=CPU核心数
// Schedulers.boundedElastic():IO密集/阻塞操作,线程数有上限
// Schedulers.single():单线程,适合低延迟定时任务
Flux.range(1, 100)
.publishOn(Schedulers.parallel()) // 下游计算在parallel
.map(n -> heavyComputation(n)) // CPU密集
.publishOn(Schedulers.boundedElastic()) // 切换到elastic
.flatMap(n -> blockingIoCall(n)) // IO阻塞操作
.subscribe();
3. 添加超时和重试
java
// 每个外部调用都应该有超时保护
Mono<User> user = webClient.get()
.uri("/users/{id}", userId)
.retrieve()
.bodyToMono(User.class)
.timeout(Duration.ofSeconds(5))
.retryWhen(Retry.backoff(3, Duration.ofMillis(200))
.maxBackoff(Duration.ofSeconds(2))
.filter(ex -> ex instanceof TimeoutException
|| ex instanceof ConnectException))
.onErrorResume(ex -> Mono.just(User.defaultUser()));
4. 使用StepVerifier编写测试
java
import reactor.test.StepVerifier;
@Test
void testFluxTransform() {
Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5)
.filter(n -> n % 2 == 0)
.map(n -> n * 10);
StepVerifier.create(flux)
.expectNext(20) // 期望第一个元素
.expectNext(40) // 期望第二个元素
.verifyComplete(); // 期望正常完成
}
@Test
void testErrorHandling() {
Mono<String> mono = Mono.<String>error(new RuntimeException("boom"))
.onErrorReturn("fallback");
StepVerifier.create(mono)
.expectNext("fallback")
.verifyComplete();
}
@Test
void testBackpressure() {
Flux<Integer> flux = Flux.range(1, 100);
StepVerifier.create(flux, 10) // 初始请求10个
.expectNextCount(10)
.thenRequest(5) // 再请求5个
.expectNextCount(5)
.thenCancel() // 取消订阅
.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();
}
5. 使用Context传递上下文
java
// 响应式链中无法使用ThreadLocal(线程会切换)
// 使用Reactor Context替代
// 写入Context
Mono.just("request-data")
.flatMap(data -> Mono.deferContextual(ctx -> {
String traceId = ctx.get("traceId");
String userId = ctx.get("userId");
return callService(data, traceId, userId);
}))
.contextWrite(ctx -> ctx
.put("traceId", UUID.randomUUID().toString())
.put("userId", currentUserId));
// 在WebFlux中自动传播(配合Micrometer)
// application.yml:
// management.tracing.enabled: true
6. 避免过深的嵌套
java
// 反模式:回调地狱的响应式版本
Mono<User> bad = getUser(id)
.flatMap(user -> getOrders(user)
.flatMap(orders -> getDetails(orders.get(0))
.flatMap(detail -> getReviews(detail)
.flatMap(reviews -> buildResponse(user, orders, detail, reviews)))));
// 正确:使用zip/then组合,保持扁平
Mono<Response> good = getUser(id)
.flatMap(user -> {
Mono<List<Order>> ordersMono = getOrders(user);
return ordersMono.flatMap(orders -> {
Mono<Detail> detailMono = getDetails(orders.get(0));
Mono<List<Review>> reviewsMono = detailMono.flatMap(this::getReviews);
return Mono.zip(detailMono, reviewsMono)
.map(tuple -> buildResponse(user, orders, tuple.getT1(), tuple.getT2()));
});
});
7. 监控与可观测性
java
// 启用Reactor指标(配合Micrometer)
Hooks.onEachOperator("metrics",
Operators.lift((scannable, subscriber) ->
new MicrometerSubscriberMetrics(subscriber, meterRegistry)));
// 手动添加指标
Flux.range(1, 1000)
.name("data-processing-pipeline") // 给流命名
.metrics() // 启用指标收集
.tap(new SignalPeek<Integer>() { // 信号窥探(调试用)
@Override
public void onNextCall(Integer value) {
log.debug("处理: {}", value);
}
@Override
public void onErrorCall(Throwable t) {
log.error("错误", t);
}
})
.subscribe();
// 关键指标:
// - reactor.flow.duration:流处理耗时
// - reactor.onNext.delay:元素间延迟
// - reactor.requested:请求量(背压监控)
// - reactor.cancelled:取消次数
8. 资源管理
java
// 使用Flux.using管理资源生命周期
Flux<String> readLines = Flux.using(
() -> new BufferedReader(new FileReader("data.txt")), // 创建资源
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 e) { /* ignore */ }
}
);
// Reactor中的连接管理(数据库连接池)
Mono.usingWhen(
connectionPool.acquire(), // 获取连接
conn -> conn.createStatement("SELECT ...") // 使用连接
.execute(),
conn -> conn.close() // 归还连接
).subscribe();
总结
响应式编程是一种强大的编程范式,核心在于:
- Reactive Streams规范定义了异步流处理的互操作标准(4个接口)
- 背压是核心创新,解决了快生产者压垮慢消费者的问题
- 推+拉结合:数据通过推传递,速率通过拉控制
- 适用场景:高并发IO、流处理、实时系统
- 不适用场景:CPU密集、简单CRUD、团队不熟悉时
- 与虚拟线程互补:虚拟线程简化并发IO,响应式处理流和背压
- 核心原则:不阻塞、有超时、有背压、有监控
