Project Reactor源码阅读-flatMap

Project Reactor源码分析系列 - SpscLinkedArrayQueue原理分析 之前做reactive streaming的调研,在回压的场景中,都会用到Queue。 周末的时候又有人问到Queue的实现原理,但是印象比较模糊,现在来具体的分析一下,Project Reactor对传统Queue的优化。 当然RxJava也是用到的同样的Queue的实现。 类说明与注释 final class SpscLinkedArrayQueue<T> extends Abst... 阅读详情

功能介绍

public final <V> Flux<V> flatMap(Function<? super T,? extends Publisher<? extends V>> mapper, int concurrency, int prefetch)

将外部Publisher发射出来的元素转换成内部Publisher,然后将这些内部的Publisher合并到一个Flux中,允许元素交错。

  1. mapper:转换函数,将外部Publisher发射出来的元素转换成一个新的Publisher。
  2. concurrency:针对外部Publisher的最大请求数量,同时也是scalarQueue队列大小。可通过reactor.bufferSize.x属性配置,默认32。
  3. prefetch:针对内部Publisher的最大请求数量,同时也是innerQueue队列大小。可通过reactor.bufferSize.small属性配置,默认256。

代码示例

public Flux<Integer> flat(int delayMillis, int i) {
    return delayPublishFlux(delayMillis, i * 10, i * 10 + 5);
}

@Test
public void test() {
    delayPublishFlux(100, 1, 6)
            .doOnRequest(r -> logLong(r, "main-request"))
            .flatMap((i) -> flat(1000, i).doOnRequest(r -> logLong(r, "inner-request"))
                    .subscribeOn(Schedulers.newElastic("inner")), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

可以看到mian-request最大是3,inner-request最大是2。证明了concurrency和prefetch的作用。

源码分析

首先看一下flatMap()操作符在装配阶段做了什么。

Flux#flatMap()

创建FluxFlatMap对象,它既是Subscriber,又是Subscription。

另外还创建了2个队列Supplier,第一个是以concurrency为大小创建的mainQueueSupplier(创建scalarQueue),另一个是以prefetch为大小的创建的innerQueueSupplier。接下来查看订阅阶段发生了什么。

Flux#subscribe()

因为FluxFlatMap实现了OptimizableOperator接口,实际的Subscriber是通过调用subscribeOrReturn()返回的。

FlatMapMain#onSubscribe()

这里最关键的是调用request()向上游请求数据,请求数量是maxConcurrency。这正是flatMap()方法传入的concurrency。当数据下发时,肯定会调用onNext()。

FlatMapMain#onNext()

  1. 将当前元素转换成一个Publisher。
  2. 如果转换后的Publisher是Callable,则直接获取元素调用tryEmitScalar()下发。
  3. 否则创建FlatMapInner对象,用它来订阅Publisher。

在前面的方法定义中提到过:flatMap()支持并发处理,允许元素交错。

看到这里,我们能得出一个推论:flatMap()是否真的会并发处理,取决转换后的Publisher是否支持异步订阅,即p.subscribe(innner)是否异步执行。

代码验证

首先回顾一下前面讲过的publishOn()和subScribeOn()工作机制。

publishOn():在onNext()、onComplete()、onError()方法进行线程切换,publishOn()使得它下游的消费阶段异步执行。 subScribeOn():在subscribe的时候进行线程切换,subscribeOn()使得它上游的订阅阶段以及整个消费阶段异步执行。

同步执行(不会发生交错)

@Test
public void testSync() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flat(1000, i), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

重点是去掉了flat()的subscribeOn()调用。

同步执行,元素没有发生交错。

subscribeOn()异步执行

@Test
public void testSubscribeOn() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flat(1000, i).subscribeOn(Schedulers.newElastic("inner")), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

异步执行,并且此时最大并发是3。

publishOn()异步执行

@Test
public void testPublishOn() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flat(10, i)
                    .publishOn(Schedulers.newElastic("inner"))
                    // 故意让下游执行慢一点
                    .doOnNext(x -> sleep(1000)), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

异步执行,并且此时最大并发是3。

通过以上代码可以得知,如果内部Publisher生产数据慢,推荐使用subscribeOn()。如果只是内部Publisher消费速度慢,推荐使用publishOn()。如果生产都消费都慢的话,两个操作符一起使用。

在调用内部Publisher的subscribe()方法之后,后续肯定会执行FlatMapInner#onSubscribe()。

FlatMapInner#onSubscribe()

与publishOn类似,FlatMapInner也支持同步队列融合、异步队列融合以及非融合三种处理方式。

如果上游的Subscription是QueueSubscription类型,则会进行队列融合。具体采用同步还是异步,取决于该QueueSubscription#requestFusion()实现。

  1. 同步队列融合:复用当前队列,然后直接调用FlatMapMain#drain()排空队列。
  2. 异步队列融合:复用当前队列,然后调用上游s.request()请求数据,请求数量是prefetch。
  3. 非融合:直接调用上游s.request()请求数据,请求数量也是prefetch。

非融合

以下代码会非融合方式执行。(和SubscribeOn()异步执行逻辑是一样的)

@Test
public void testNoFused() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flat(100, i)
                    .subscribeOn(Schedulers.newElastic("inner")), 3, 2)
            .subscribe(i -> {
                // 消费慢一点,innerQueue更容易有数据积压
                sleep(1000);
                logInt(i, "消费");
            });
    sleep(10000);
}

FlatMapInner#onNext()

此时onNext()方法入参就是内部Publisher实际下发的元素,继续调用FlatMapMain#tryEmit()下发。

FlatMapMain#tryEmit()

上面代码逻辑其实主要由两种情况组成:(后面两大块逻辑是差不多的)

  1. 可能直接调用下游Subscriber#onNext()继续下发元素。
  2. 创建队列,然后将元素加入队列,视情况调用drainLoop()排空队列。

具体取决于数据生产以及消费的速度: 1、如果消费速度大于生产速度,没有数据积压,则直接调用下游Subscriber#onNext()进行下发。 2、如果消费速度跟不上生产速度,元素会直接先保存到innerQueue中,然后在wip==0时调用drainLoop()排空队列。

FlatMapMain#tryEmitScalar()

前面提到过,如果转换后的Publisher是Callable,会执行tryEmitScalar()方法。该方法做的事情跟tryEmit()处理逻辑基本一致,主要差别就是处理的元素和使用的队列不同。

tryEmitScalar()处理的元素是内部Publisher直接调用call()获取的,而tryEmit()是内部Publisher向下游发送的。tryEmitScalar()使用scalarQueue缓存元素,而tryEmit()使用innerQueue。

FlatMapMain#drainLoop()

drainLoop()逻辑非常多,截取部分关键代码:

  1. 排空innerQueue中的元素,下发给下游。
  2. 请求内部Publisher下发元素,请求数量就是本次innerQueue排出数量。
  3. 请求外部Publisher下发元素,请求数量是可补充数量,不会超过concurrency。

另外还有一些完成和取消控制。

同步队列融合

以下代码会以同步队列融合方式执行。

@Test
public void testSyncFused() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flatRange(i), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

复用当前队列,然后直接调用FlatMapMain.drain()排空队列。

FlatMapMain#drain()

依然是调用drainLoop()排空队列中的元素。注意,同步队列融合没有request()过程,直接在onSubscribe()阶段进行元素下发。

异步队列融合

以下代码会以异步队列融合方式执行。

@Test
public void testAsyncFused() {
    delayPublishFlux(100, 1, 6)
            .flatMap((i) -> flatRange(i).publishOn(Schedulers.newElastic("inner")), 3, 2)
            .subscribe(i -> logInt(i, "消费"));
    sleep(10000);
}

复用当前队列,然后调用上游s.request()请求数据,请求数量是prefetch。后续肯定会调用FlatMapInner#onNext()。

FlatMapInner#onNext()

此时入参为null,然后调用了FlatMapMain.drain(),排空队列元素。异步队列融合会复用队列,上游实际发送是null,可以将其理解成一个信号,告知下游排空队列中的元素。

Reactor 响应式编程 之 flatMap vs map 详解 本文介绍 Project Reactor 中的 map 和 flatMap 操作符之间的差异。它们在Mono和Flux类中定义,用于在处理流时转换项。 阅读详情

相关推荐

深入理解Flux中的flatMap操作符

在响应式编程中,flatMap是一个非常重要的操作符,它允许我们将集合中的元素转换为Publisher,然后合并这些Publisher的输出。本文将通过实际代码示例,详细解析flatMap的不同用法和其背后的原理。

m0_62153576的博客 825

Reactor3 源码解析二十二: FluxFlatMap源码剖析

flatMap 和 flatMapSequential 操作符把流中的每个元素转换成一个流,再把所有流中的元素进行合并。flatMapSequential 和 flatMap 之间的区别与 mergeSequential 和 merge 之间的区别是一样的。 Flux.just(5, 10) .flatMap(x -> Flux.interval(Duration.ofMillis(x * 10), Duration.ofMillis(100)).take(x))

八荒六合唯我独尊 1123

gradle报错:A problem occurred configuring root project ‘My Application‘.

即使翻译了,我开始也看不懂,到处找网上教程,但都是改环境变量。马虎的我即使看了很多遍,改了很多遍也没反应过来,今天第一次搞gradle,突然就报错了,打印出。好了我就继续看项目了。

m0_59878789的博客 4091

一看秒懂 Flux.map()、 Flux.flatMap()、 Flux.buffer() 的区别!!!

【代码】一看秒懂 Flux.map()、 Flux.flatMap()、 Flux.buffer() 的区别!!!

heshiyuan1406146854的博客 1068

MATLAB实现灰雁优化算法(GGO):从原理到实战的详细级教程

灰雁优化算法(GGO)是一种新型群体智能算法,模拟灰雁迁徙和觅食行为。算法通过领导更替机制、V形编队飞行和群体觅食策略,平衡全局探索与局部开发能力,具有收敛精度高、避免局部最优等特点。在MATLAB环境中,算法实现包括初始化种群、迭代更新领导雁、执行编队飞行和觅食行为等核心模块。用户可根据问题维度设置参数,通过收敛曲线和可视化分析评估性能。关键参数如领导更替频率、V形编队强度和觅食强度需根据问题复杂度调整,以获得最佳优化效果。该算法适用于各类优化问题,具有较好的鲁棒性和稳定性。

fx666_的博客 1212

TSMaster实战:如何用LIN总线唤醒你的汽车电子系统(附波形分析)

本文通过TSMaster工具实战解析LIN总线唤醒机制。详细介绍了LIN网络唤醒的协议要求、时序参数计算,并演示了如何在TSMaster中配置唤醒信号、捕获分析波形,以及验证休眠流程。文章结合常见问题排查,为汽车电子工程师提供了从理论到实践的完整调试指南。

weixin_28308325的博客 464

Java开发者进阶指南:从源码到架构的实战项目深度解析

在Java技术生态中,深入理解核心框架与分布式系统原理是开发者能力提升的关键路径。通过剖析优秀开源项目的源码,可以系统性地掌握从基础算法、设计模式到高并发、微服务架构的设计思想与实践。例如,研究Spring Framework的Bean生命周期和AOP代理机制,能深刻理解控制反转与面向切面编程的技术价值;分析Apache Dubbo的服务暴露、集群容错流程,则能洞悉分布式服务治理在高可用场景下的应用。这种从概念到原理,再到具体技术实现的学习方法,尤其适用于解决工程实践中的复杂问题,如利用Reactor理解响

weixin_30568591的博客 380

开关电源辅助电源设计全流程拆解

地类型定义包含电路功率地(PGND)高压侧地,与主功率电路地相连整流桥负极、原边电感地、UC3842的功率地信号地(SGND)低压侧地,与控制/采样电路地相连副边整流管地、TL431地、MCU/运放地。

lyzh971103的博客 1476

5 分钟快速上手 r2dbc-mysql:从零搭建第一个响应式 MySQL 应用

r2dbc-mysql 是 R2DBC(响应式关系型数据库连接)规范下的 MySQL 实现驱动,它让 Java 开发者可以用非阻塞、响应式编程的方式操作 MySQL 数据库。与传统的 JDBC 阻塞模型不同,r2dbc-mysql 基于 Project Reactor,单线程即可支撑高并发,是 Spring WebFlux 生态访问 MySQL 的主流选择。本文将以最短路径带你完成依赖引入、连接配

gitblog_01105的博客 967

RISC-V— 架构基础知识学习

蜂鸟 E200 系列是一个开源的RISC-V 处理器。蜂鸟 E200 系列由中国大陆研发团队开发,用户能够轻松与开发人员取得交流和支持。RI蜂鸟 E200 处理器研发团队拥有在国际一流公司多年开发处理器的经验,使用稳健的Verilog 2001 语法编写的可综合 RTL 代码,以工业级标准进行开发。蜂鸟 E200 的代码为人工编写,添加丰富的注释且可读性强,非常易于理解。

deilt 1万+

保姆级教程:2025新版JavPlayer 3.01整合修复版安装配置全流程(N卡/A卡/CPU通用)

本文提供2025新版JavPlayer 3.01整合修复版的保姆级安装配置教程,涵盖N卡、A卡和CPU环境。详细讲解硬件要求、分步安装流程、显卡优化配置及超分辨率模型选择,帮助用户高效完成视频修复任务。

weixin_42665255的博客 1906

2024 Java面试核心考点与实战解析

Java作为企业级开发的主流语言,其技术体系涵盖JVM原理、并发编程、框架源码等核心领域。理解JVM内存模型与GC算法是性能优化的基础,而线程池实现与锁优化策略则是高并发系统的关键。分布式架构中的CAP理论与事务解决方案,以及数据库索引优化与SQL调优技巧,都是构建稳定高效系统的必备知识。通过分析Spring循环依赖处理等框架源码,可以深入理解设计模式的应用价值。本文结合大厂真实面试题,详解JVM内存排查、G1/CMS GC对比、线程池参数配置等高频考点,并给出秒杀系统设计等典型场景的解决方案。掌握这些核心

weixin_34006965的博客 442

java实现长短期记忆人工神经网络LSTM(附完整源码)

java实现长短期记忆人工神经网络LSTM(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1695

Java中级开发面试核心:并发编程与JVM调优实战

并发编程是Java开发的核心技能,涉及线程安全、锁优化等关键技术。通过synchronized和ReentrantLock等机制实现线程同步,其底层原理涉及JVM monitor和AQS队列。JVM调优则关注内存管理、垃圾回收策略等,合理配置参数可显著提升系统性能。这些技术在电商秒杀、金融交易等高并发场景中尤为重要。本文结合面试真题,深入解析Java并发编程三大核心问题与JVM性能调优实战要点,帮助开发者掌握系统设计关键能力。

weixin_34199335的博客 405

LSTM 深度解析从原理推演到现代序列建模的演进

LSTM(Long Short-Term Memory,长短期记忆网络)是一种特殊的递归神经网络(RNN),它通过引入记忆单元(Cell State)来解决普通RNN在长序列数据处理中的梯度消失问题。LSTM可以在长时间的序列中捕捉依赖关系,是一种非常适合处理时间序列、自然语言处理、语音识别等任务的深度学习模型。在LSTM中,记忆单元(Cell State)是一个贯穿整个序列的数据通道,用于存储和传递关键信息。记忆单元在LSTM的时间步之间保持信息的持久性,可以看作是一个长期记忆的“容器”。

hello.reader 3万+

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

reactive flux 的map 和flatmap

操作 对每个元素应用转换函数,返回新元素 对每个元素应用转换函数,返回一个新的流,然后将多个流“扁平化”成一个流。适用场景 元素的同步转换 异步操作或需要返回多个元素(例如,查询数据库、调用外部 API 等)在反应式编程中,flatMap 和 map 都是用于转换数据流的操作符,但它们的工作方式和用途有显著的区别。异步处理 适合同步处理(一个元素映射到另一个元素) 适合异步处理(一个元素映射到一个新的流)使用 flatMap(异步操作)使用 map(同步操作)

heeheeai的博客 746

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

聊聊Flux的compose与flatMap操作

序 本文主要研究下Flux的compose与flatMap操作 compose Flux.just("1", "2","3") .log() .compose(stringFlux -> { LOGGER.info...

weixin_33807284的博客 304

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

reactor3 flux的map与flatMap的区别

序 本文主要研究一下flux的map与flatMap的区别 map @Test public void testMap() throws InterruptedException { Flux.just(1, 2, 3, 4) .log() .map(i -&gt; { ...

weixin_33916256的博客 2201

Project Reactor源码分析-subscribeOn

(弹性线程池)有关,它底层是由单线程池组成的,下一篇文章会深入分析。操作符的消费阶段实现简单,都是直接将元素下发给下游。操作符,顺便探究一下上述问题发生的原因。)继续向上游请求数据,因此不会造成阻塞。操作符在装配阶段做了什么,直接查看。在红框时间段内,消费者被阻塞了!的时候,我们已经知道此时在。下面我们从源码角度分析。装配阶段重点是创建了。对象,该类同样实现了。

老男孩的架构路 448

java publisher_Combine -- 常见的 Publisher 及 Operator

一、 Publisher1、EmptyEmpty 是一个最简单的发布者,它只在被订阅的时候发布一个完成事件 (receivefinished)。这个 publisher 不会输出任何的 output 值,只能用于表示某个事件已经发生。2、Just表示一个单一的值,在被订阅后,这个值会被发送出去,紧接着是 finished3、序列 PublisherPublishers.Sequence 接受一个 ...

weixin_34620658的博客 661

Reactor3 源码解析七: FluxPublishOn源码剖析(2)

Flux.create(emitter -> { for (int i = 0; i <= 20; i++) { if (emitter.isCancelled()) { return; } System.out.println(Thread.currentThread().getName() + ":Source created " + i); emitter.next(i); } }).doOnNext(s -...

八荒六合唯我独尊 697

Flux中的map、flatMap、concatMap、flatMapMany的区别

map:用于一对一的转换,返回一个新的Flux,元素顺序不变。flatMap:用于一对多的转换,返回一个新的Flux,元素顺序可能发生变化。concatMap:用于一对多的转换,返回一个新的Flux,元素顺序与原始Flux中的元素顺序保持一致。操作符并发度顺序保证适用场景flatMap高不保证无顺序要求的并发处理concatMap低 (1)严格保证需顺序处理的场景高保证输出顺序需并发处理但保持顺序选择使用哪种方法取决于具体的业务需求和对元素顺序的要求。

qq_32907491的博客 406

Project Reactor中 map、flatMap、concatMap 和 flatMapSequential 的区别

(Reactive Streams 的实现库,常用于 Spring WebFlux)中,是常用的操作符,但它们的功能和行为有显著区别。根据你的需求选择合适的操作符。如果有更多问题,欢迎继续提问!

huachangzai的博客 1819

Spring5中Reactor编程的Mono与Flux类源码解读

Spring5的函数式编程使用了ProjectReactor工程的类,使用最为多的就是Mono和Flux类型,其中Mono是针对0到1个元素进行操作,Flux是针对多个元素进行操作。要使用这两个类前提是自己要有jdk8,Lambda,函数式编程的基础,否则请先学习了以上知识再来接触Spring5的Mono和FLux。 我们针对Mono类的一行代码,来进行源码分析与讲解。代码如下: Mono.......

lz710117239的博客 2万+

聊聊FluxFlatMap的concurrency及prefetch参数

序 本文主要研究下FluxFlatMap的concurrency及prefetch参数 实例 @Test public void testConcurrencyAndPrefetch(){ int concurrency = 3; int prefetch = 6; Flux.range(1,100) ...

weixin_34372728的博客 333

Reactor3 源码解析二十三: FluxConcatMap源码剖析

Flux.just(5, 10) .concatMap(x -> Flux.interval(Duration.ofMillis(x * 10), Duration.ofMillis(100)).take(x)) .subscribe(System.out::println); Thread.sleep(1000*60l); concatMap操作与flatMapSequential操作非常相似,但在底层实现上有区别。flatMapSequential是...

八荒六合唯我独尊 1557
上一篇: 搞不懂,为啥redis操作对象的注入方式跟平常的不一样?
下一篇: 全面互联网时代背景下,一个好的Java程序员需要掌握哪些核心技术
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值