Java深入解析篇十九之Project Reactor

Java深入解析篇十九之Project Reactor

一、Project Reactor 概述

1.1 什么是 Project Reactor

Project Reactor 是一个基于 Reactive Streams 规范的响应式编程库,是 Spring WebFlux 的底层基础。它提供了两个核心类型 MonoFlux,用于构建非阻塞、异步的应用程序。

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 个元素的异步序列,支持背压。元素按顺序发射,以 onCompleteonError 信号终止。

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
相关推荐
mqiqe5 分钟前
AgentScope Java 2.0 集成 Chat Completions Web:一行依赖让你的 Agent 变身 OpenAI 兼容服务
java·开发语言·前端
qq_5896660515 分钟前
Java微服务介绍及应用
java·开发语言·微服务
OPEN-F17 分钟前
C++进阶教程:运算符重载与类型转换
java·c++·算法
小小数媒成员22 分钟前
存储器层级结构
java·服务器·前端
uoKent26 分钟前
c++中的extern关键字
开发语言·c++
牧杉-惊蛰30 分钟前
将数组对象根据自定义排列
java·开发语言·算法
砚底藏山河43 分钟前
【量化纯GET实战 #04】批量快照:一次 HTTP GET 取多只股票
java·python·金融·eclipse
君顾11 小时前
家校托管互通:从需求分析到系统架构的落地实践
java·开发语言·托管
QQ_21696290961 小时前
【源码编号:project93375】SpringBoot汽车维修管理信息系统:客户车辆、维修预约、工单派发、配件结算全流程实战
java·spring boot·后端·汽车·springboot·需求分析
jimy11 小时前
c++隐式移动构造、强制拷贝省略、返回具名局部变量
开发语言·c++