一文搞懂 Flux 和 Mono 底层原理

 

Flux 和 Mono 是 Project Reactor(Spring WebFlux 的底层响应式库)的两大核心类型,它们都实现了 Reactive Streams 规范 中的 Publisher<T> 接口,本质上是一种 惰性、声明式、基于发布-订阅模型的异步数据流抽象。


一、Flux 与 Mono 是什么?

类型元素数量语义类比典型场景
Flux<T> 0 ~ N 异步版 Iterable<T> 列表查询、事件流、WebSocket、SSE
Mono<T> 0 或 1 异步版 Optional<T> / CompletableFuture<T> 单条查询、HTTP 调用、保存结果、完成信号

两者都是抽象类,所有操作符(map、filter、flatMap 等)都返回新的 Flux/Mono 实例,因此可以无限链式组合。


二、理论基石:Reactive Streams 规范

Reactor 的所有实现完全对齐 Reactive Streams 规范,该规范定义了四个核心接口:

  • Publisher<T>:数据发布者(生产者),提供 subscribe(Subscriber) 方法。
  • Subscriber<T>:数据订阅者(消费者),接收 onSubscribe、onNext、onError、onComplete 四种信号。
  • Subscription:订阅句柄,提供 request(n)(请求 n 个数据)和 cancel()(取消订阅)。
  • Processor<T,R>:兼具 Publisher 和 Subscriber 能力,用于中间处理。

信号协议为:

onSubscribe → onNext × (0..N) → [onComplete | onError]

onComplete 和 onError 互斥,最多出现一次,之后不再有任何信号。


三、核心运行机制:订阅 → 请求 → 推送

1. 惰性构建:不订阅,不干活

调用 Flux.just()、Mono.fromCallable() 等操作符时,不会立即执行任何操作,只是组装出一条算子责任链。甚至连 Flux.interval() 这种定时器,在没人订阅时根本不会开始计时。

2. subscribe() 触发反向订阅链

调用 subscribe() 后,订阅从下游向上游反向建立连接:

Subscriber ← take ← filter ← map ← 数据源(FluxArray)

每个操作符都会包装传入的 Subscriber,创建自己的内部 Subscriber,然后向上游订阅。最终数据源收到被层层包装后的 Subscriber,开始发送数据。

3. onSubscribe:交出控制权

数据源通过 onSubscribe(Subscription) 将 Subscription 对象传递给下游,下游拿到控制权后,可以决定何时、请求多少数据。

4. request(n):背压的核心入口

下游调用 request(n) 向上游声明"我能处理 n 个数据",上游据此推送对应数量的 onNext。只要不调用 request(),数据就永远停在上游。

5. 终止信号

数据发完触发 onComplete(),出错触发 onError(),同时自动销毁 Subscription、释放资源。


四、背压(Backpressure):按需拉取,而非硬推

背压是 Reactor 区别于 CompletableFuture 和普通回调的本质特征。传统异步模型中,生产者可以无限制地推送数据,容易压垮消费者;而 Reactor 通过 request(n) 实现了精确的流量控制。

常见背压策略:

策略行为
onBackpressureBuffer 缓存暂时无法处理的数据,可设容量上限
onBackpressureDrop 丢弃下游来不及处理的数据
onBackpressureLatest 只保留最新数据,丢弃旧数据
onBackpressureError 超出能力时直接报错
limitRate 内部自动分批请求,采用预取策略平衡吞吐与内存

在 Spring WebFlux 中,当 HTTP 客户端消费响应流过慢时,底层的 Netty 连接会自动暂停读取,避免内存溢出。


五、算子链:每个算子都是一层 Publisher

操作符不会立即执行,而是生成新的 Publisher,将上下游串成一条链。数据流动时,从数据源一路向下经过每个算子的处理。

  • map:同步转换,通过 FluxMap 逐个拦截 onNext,转换后继续向下游推送。
  • flatMap:异步扁平转换,通过 FluxFlatMap 将内部产生的新 Publisher 订阅并合并到主数据流中,底层维护并发队列保证数据有序或无序推送,是异步编排的核心算子。
  • filter:根据条件决定是否放行元素。
  • retryWhen:捕获 onError 信号,通过新的订阅实例重新发起数据流订阅,支持自定义重试次数、间隔、异常匹配规则。
  • onErrorResume:异常兜底降级,拦截异常后切换备用数据流,保证链路不中断。

Operator Fusion(操作符融合):Reactor 还会对相邻操作符进行融合优化,减少中间环节的数据拷贝和方法调用,降低层层包装带来的性能开销。


六、线程调度:Scheduler 体系

Reactor 不直接操作线程,而是通过 Scheduler 管理执行上下文。

核心调度器

调度器适用场景说明
Schedulers.parallel() CPU 密集型 固定大小线程池,默认线程数 = CPU 核数
Schedulers.boundedElastic() 阻塞 I/O 有界弹性线程池,适合 JDBC、同步 HTTP 调用
Schedulers.single() 串行执行 单线程调度
Schedulers.immediate() 当前线程 不切换线程

subscribeOn vs publishOn

这是最容易混淆的两个操作符:

操作符作用范围类比
subscribeOn(S) 影响整个流的起始执行线程,无论放在链的哪个位置 决定产品在哪个工厂开始生产
publishOn(S) 只影响它下游的操作符 从这里开始换哪家快递公司派送
Flux.fromIterable(ids)
    .publishOn(Schedulers.boundedElastic())  // 从这里开始切换到弹性线程池
    .map(id -> blockingQuery(id))             // 阻塞调用安全执行
    .publishOn(Schedulers.parallel())         // 再切换到并行线程池
    .map(result -> cpuIntensiveProcess(result)) // CPU 密集型计算
    .subscribeOn(Schedulers.single())         // 流的起点在单线程上
    .subscribe();

⚠️ 常见陷阱:把阻塞调用(如 JDBC)放到 parallel() 线程池上会导致线程被吃光,必须使用 boundedElastic()。


七、完整执行链路图

构建阶段(惰性)                    订阅阶段(反向建立连接)               执行阶段(正向推送数据)
                                                                         
Flux.just(1,2,3)                  Subscriber 调用 subscribe()          数据源发出 onNext(1)
  .map(x -> x*2)                      ↓                                   ↓
  .filter(x -> x>2)              下游向上游反向订阅                      map 转换: onNext(2)
  .subscribe()                        ↓                                   ↓
                                 数据源创建 Subscription                filter 过滤: 2≤2, 丢弃
                                 通过 onSubscribe 传递给下游              ↓
                                                                      数据源发出 onNext(2)
                                                                           ↓
                                                                       map 转换: onNext(4)
                                                                           ↓
                                                                       filter 放行: 4>2 ✓
                                                                           ↓
                                                                       Subscriber.onNext(4)
                                                                           ↓
                                                                       ... 直至 onComplete

八、总结

Flux 是多元素异步流,Mono 是单元素异步流;它们的底层都基于 Reactive Streams 的发布-订阅模型,通过以下五大机制协同工作:

  1. 惰性订阅:不 subscribe() 就不执行,连数据源都不会启动。
  2. 算子链:每个操作符返回新 Publisher,形成责任链,支持 Operator Fusion 优化。
  3. Subscription:连接 Publisher 和 Subscriber 的契约,提供 request(n) 和 cancel()。
  4. 背压:下游通过 request(n) 按需拉取,从根本上解决流量过载问题。
  5. Scheduler:通过 subscribeOn 和 publishOn 精细控制线程执行上下文。

这套设计让 Reactor 能够用极少数线程高效处理海量并发连接,将系统瓶颈从线程数量转移到 CPU 核心处理能力上,这也是 Spring WebFlux 和 Spring Cloud Gateway 高吞吐能力的底层根源。


原理看完了,要不要我帮你整理一份 Flux/Mono 实战避坑指南?比如线程池误用、背压配置、异常处理这些高频踩坑点。

posted @ 2026-09-29 08:49  七星6609  阅读(5)  评论(0)    收藏  举报