Reactor响应式编程中Sinks.Many

Reactor响应式编程中Sinks.Many的核心概念、类型、用法,以及它和FluxSink的区别,从「定义定位→核心类型→实战示例→对比FluxSink→使用场景」逐步讲解,用通俗易懂的语言+业务场景示例,帮你掌握这个高级发射器的核心用法。

一、核心定义:Sinks.Many 是什么?

Sinks.Many是Reactor 3.4+版本引入的高级多订阅者发射器 ,是对基础FluxSink的增强封装,核心定位是:

  • 专门处理「0到N个元素的异步序列」,支持多个订阅者同时消费数据流;
  • 遵循Reactive Streams规范,原生支持背压(Backpressure);
  • 提供多种「订阅者策略」,可精细化控制消息如何分发给不同订阅者(比如是否补发历史消息、是否允许多订阅者);
  • 本质是「生产者-消费者模型」的高级实现,解决了Flux.create()在多订阅者场景下灵活性不足的问题。

简单比喻:

  • FluxSink = 单水龙头(仅适配单个消费端,分发规则固定);
  • Sinks.Many = 智能分水器(支持多个消费端,可配置给不同消费端发"新水"/"历史水")。

二、核心类型:4种订阅者策略(关键)

Sinks.Many的核心价值在于提供了4种不同的策略,适配不同的多订阅者场景,所有类型都通过Sinks.many()静态方法创建:

策略类型 核心特性 适用场景
unicast() 仅支持1个订阅者,多订阅会抛异常;订阅者接收订阅后所有元素(无历史补发) 一对一的专属数据流(如单个客户端的实时订单)
multicast() 支持多个订阅者 ;可配置是否补发历史消息(通过replay()控制) 一对多广播(如奶茶店新品通知给所有用户)
broadcast() 轻量级多订阅者广播;不补发历史消息,仅发订阅后的新元素 实时状态推送(如库存实时变化)
directAllOrNothing() 极简多订阅者模式;要么所有订阅者都收到元素,要么都收不到(无背压) 低延迟、无丢失要求的场景(如实时日志)
关键补充:multicast() 的 replay 配置

multicast()是最常用的类型,通过replay()可控制历史消息补发:

  • replay(0):不补发历史,仅发订阅后的新元素;
  • replay(5):补发订阅前最近的5条消息;
  • replayAll():补发所有历史消息(需注意内存占用)。

三、实战示例:结合奶茶店场景

以下示例基于Reactor 3.5+,用「奶茶店新品通知」场景演示核心用法:

前置依赖(同Flux)
xml 复制代码
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-core</artifactId>
    <version>3.5.11</version>
</dependency>
示例1:multicast() 广播新品(支持补发历史)
java 复制代码
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;

public class SinksManyDemo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建multicast类型的Sinks.Many,补发最近2条历史消息
        Sinks.Many<String> productSink = Sinks.many()
                .multicast()
                .onBackpressureBuffer(2); // replay=2,背压策略为缓存

        // 2. 转为Flux供订阅者消费(核心:Sinks.Many可转为Flux)
        Flux<String> productFlux = productSink.asFlux();

        // 3. 生产者先发射2条新品消息(此时暂无订阅者)
        productSink.tryEmitNext("新品1:云桃乌龙");
        productSink.tryEmitNext("新品2:桂花云露");

        // 4. 第一个订阅者(用户A)订阅:会收到补发的2条历史消息
        System.out.println("===== 用户A订阅 =====");
        productFlux.subscribe(
            msg -> System.out.println("用户A收到:" + msg),
            error -> System.err.println("用户A出错:" + error),
            () -> System.out.println("用户A流结束")
        );

        // 5. 生产者再发射1条新品(所有订阅者都收到新消息)
        productSink.tryEmitNext("新品3:云雾观音");

        // 6. 第二个订阅者(用户B)订阅:补发最近2条(新品2+新品3)
        Thread.sleep(1000); // 模拟延迟
        System.out.println("\n===== 用户B订阅 =====");
        productFlux.subscribe(
            msg -> System.out.println("用户B收到:" + msg),
            error -> System.err.println("用户B出错:" + error),
            () -> System.out.println("用户B流结束")
        );

        // 7. 触发流完成
        productSink.tryEmitComplete();
    }
}

执行结果

复制代码
===== 用户A订阅 =====
用户A收到:新品1:云桃乌龙
用户A收到:新品2:桂花云露
用户A收到:新品3:云雾观音

===== 用户B订阅 =====
用户B收到:新品2:桂花云露
用户B收到:新品3:云雾观音
用户A流结束
用户B流结束
示例2:unicast() 单订阅者(多订阅抛异常)
java 复制代码
// 创建unicast类型,仅支持1个订阅者
Sinks.Many<String> unicastSink = Sinks.many()
        .unicast()
        .onBackpressureBuffer();

// 第一个订阅者正常
unicastSink.asFlux().subscribe(msg -> System.out.println("订阅者1:" + msg));

// 第二个订阅者会抛异常
try {
    unicastSink.asFlux().subscribe(msg -> System.out.println("订阅者2:" + msg));
} catch (Exception e) {
    System.err.println("订阅失败:" + e.getMessage()); // 输出:Only one subscriber allowed
}

// 生产者发射元素
unicastSink.tryEmitNext("仅订阅者1能收到的消息");
示例3:发射方法选择(tryEmitXxx vs emitXxx)

Sinks.Many提供两种发射元素的方式,需根据场景选择:

方法 特性
tryEmitNext() 非阻塞,返回Sinks.EmitResult枚举(成功/失败),不会抛异常
emitNext() 阻塞/异步(取决于策略),失败时抛异常,支持自定义重试逻辑

示例:

java 复制代码
// tryEmitNext() 非阻塞发射
Sinks.EmitResult result = productSink.tryEmitNext("新品4:云谷燕麦");
if (result == Sinks.EmitResult.FAIL_OVERFLOW) {
    System.err.println("元素发射失败:背压溢出");
}

// emitNext() 带重试逻辑发射
productSink.emitNext("新品5:云峰山茶", 
    (signalType, emitResult) -> emitResult == Sinks.EmitResult.FAIL_OVERFLOW);

四、Sinks.Many vs FluxSink:核心区别

维度 Sinks.Many FluxSink(Flux.create())
订阅者数量 支持多订阅者(可配置策略) 仅支持隐式多订阅,但无精细化控制
历史消息补发 支持(multicast的replay配置) 不支持,仅发订阅后的新元素
发射结果处理 提供tryEmitXxx,返回枚举,容错性强 无返回值,失败直接抛异常
状态监控 支持(如currentSubscriberCount() 无原生状态监控能力
适用场景 多订阅者、需补发历史、状态监控的场景 单订阅者、简单手动发射的场景

五、核心使用场景

  1. 一对多广播 :如奶茶店新品通知给所有在线用户(用multicast(replay(0)));
  2. 带历史补发的订阅 :如用户刚上线,补发最近的3条库存预警消息(用multicast(replay(3)));
  3. 一对一专属流 :如单个用户的实时订单推送(用unicast());
  4. 高容错的元素发射 :需捕获发射失败(如背压溢出),用tryEmitXxx做降级处理;
  5. 状态监控 :需统计当前订阅者数量(如productSink.currentSubscriberCount())。

总结

  1. 核心定位Sinks.Many是Reactor的高级多订阅者发射器,是FluxSink的增强版,主打多订阅者场景的精细化控制;
  2. 核心选择
    • 单订阅者、简单场景 → 用Flux.create() + FluxSink
    • 多订阅者、需补发历史、高容错 → 用Sinks.Many(优先multicast());
  3. 关键技巧
    • 发射元素优先用tryEmitXxx,避免异常中断;
    • multicast()replay配置需根据内存情况选择(避免replayAll()导致OOM);
    • 始终配置背压策略,平衡生产/消费速率。

Sinks.Many是Reactor中实现"发布-订阅"模式的核心工具,在实时消息推送、多客户端状态同步、分布式事件通知等场景中不可或缺。

相关推荐
半桔2 小时前
【设计模式】策略模式:可插拔算法,从硬编码到灵活适配,体会“算法解耦“思想
java·c++·算法·设计模式·策略模式
一 乐2 小时前
在线考试|基于springboot + vue在线考试系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
Yang-Never2 小时前
Android 应用启动 -> Android 多种方式启动同一进程,Application.onCreate() 会多次执行吗?
android·java·开发语言·kotlin·android studio
期待のcode2 小时前
Java 共享变量的内存可见性问题
java·开发语言
会游泳的石头2 小时前
深入剖析 Java 长连接:SSE 与 WebSocket 的实战陷阱与优化策略
java·开发语言·websocket
lllljz2 小时前
blenderGIS出现too large extent错误
java·服务器·前端
qq_12498707532 小时前
基于spring boot的调查问卷系统的设计与实现(源码+论文+部署+安装)
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
what丶k2 小时前
深入理解Java NIO:从原理到实战的全方位解析
java·开发语言·nio
血小板要健康2 小时前
笔试面经2(上)(纸质版)
java·开发语言