Java深入解析篇十八之响应式编程

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必须>0
  • request()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();

总结

响应式编程是一种强大的编程范式,核心在于:

  1. Reactive Streams规范定义了异步流处理的互操作标准(4个接口)
  2. 背压是核心创新,解决了快生产者压垮慢消费者的问题
  3. 推+拉结合:数据通过推传递,速率通过拉控制
  4. 适用场景:高并发IO、流处理、实时系统
  5. 不适用场景:CPU密集、简单CRUD、团队不熟悉时
  6. 与虚拟线程互补:虚拟线程简化并发IO,响应式处理流和背压
  7. 核心原则:不阻塞、有超时、有背压、有监控
相关推荐
看浪的路人1 小时前
第5讲:代码审查与 Bug 检测
开发语言·windows·python
netccdn1 小时前
Hough变换检测直线(Matlab)
开发语言·计算机视觉·matlab
有点。1 小时前
C++深度优先搜索(三)
开发语言·c++·深度优先
天空'之城1 小时前
C 语言工业级通用组件手写 25:简易日志系统
c语言·开发语言·日志系统·嵌入式调试·调试打印
程序喵大人1 小时前
【C++进阶】STL算法与函数对象 -【C++进阶】STL算法与函数对象
开发语言·c++·算法
IKUN家族1 小时前
Spring MVC(五)
java·spring·mvc
weixin_BYSJ19872 小时前
【java项目分享】springboot阅读推荐平台10600
java·javascript·spring boot·python·django·flask·php
小 黄 鸡2 小时前
JVM 核心总结
java
牛马也想出海2 小时前
使用Playwright被检测为机器人的原因及反检测方案
开发语言·网络·人工智能·机器人·php