Reactor 多任务并发执行且结果按顺序返回第一个

目录
  • 1 场景
  • 2 创建 service
    • 2.1 创建基本接口和实体类
    • 2.2 创建 service 实现
  • 3 主体方法
  • 4 实现异步
    • 4.1 subcribeOn 实现异步
    • 4.2 CompletableFuture 实现异步

1 场景

调用多个平级服务,按照服务优先级返回第一个有效数据。

具体case:一个页面可能有很多的弹窗,弹窗之间又有优先级。每次只需要返回第一个有数据的弹窗。但是又希望所有弹窗之间的数据获取是异步的。这种场景使用 Reactor 怎么实现呢?

2 创建 service

2.1 创建基本接口和实体类

public interface TestServiceI {
    Mono request();
}

提供一个 request 方法,返回一个 Mono 对象。

@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class TestUser {
    private String name;
}

2.2 创建 service 实现

@Slf4j
public class TestServiceImpl1 implements TestServiceI {
    @Override
    public Mono request() {
        log.info("execute.test.service1");
        return Mono.fromSupplier(() -> {
                    try {
                        System.out.println("service1.threadName=" + Thread.currentThread().getName());
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "";
                })
                .map(name -> {
                    return new TestUser(name);
                });
    }
}

第一个 service 执行耗时 500ms。返回空对象;

创建第二个 service 执行耗时 1000ms。返回空对象;代码如上,改一下sleep时间即可。

继续创建第三个 service 执行耗时 1000ms。返回 name3。代码如上,改一下 sleep 时间,以及返回为 name3。

3 主体方法

public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        TestServiceI testServiceImpl4 = new TestServiceImpl4();
        TestServiceI testServiceImpl5 = new TestServiceImpl5();
        TestServiceI testServiceImpl6 = new TestServiceImpl6();
        List<TestServiceI> serviceIList = new ArrayList<>();
        serviceIList.add(testServiceImpl4);
        serviceIList.add(testServiceImpl5);
        serviceIList.add(testServiceImpl6);

    // 执行 service 列表,这样有多少个 service 都可以
        Flux<Mono<TestUser>> monoFlux = Flux.fromIterable(serviceIList)
                .map(service -> {
                    return service.request();
                });
    // flatMap(或者flatMapSequential) + map 实现异常继续下一个执行
        Flux flux = monoFlux.flatMapSequential(mono -> {
            return mono.map(user -> {
                        TestUser testUser = JsonUtil.parseJson(JsonUtil.toJson(user), TestUser.class);
                        if (Objects.nonNull(testUser) && StringUtils.isNotBlank(testUser.getName())) {
                            return testUser;
                        }
            // null 在 reactor 中是异常数据。
                        return null;
                    })
                    .onErrorContinue((err, i) -> {
                        log.info("onErrorContinue={}", i);
                    });
        });
        Mono mono = flux.elementAt(0, Mono.just(""));
        Object block = mono.block();
        System.out.println(block + "blockFirst 执行耗时ms:" + (System.currentTimeMillis() - startTime));
    }
  • 1、Flux.fromIterable 执行 service 列表,可以随意增删 service 服务。
  • 2、flatMap(或者flatMapSequential) + map + onErrorContinue 实现异常继续下一个执行。具体参考:Reactor中的onErrorContinue 和 onErrorResume
  • 3、Mono mono = flux.elementAt(0, Mono.just("")); 返回第一个正常数据。

执行输出:

20:54:26.512 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
20:54:26.553 [main] INFO com.geniu.reactor.TestServiceImpl1 - execute.test.service1
service1.threadName=main
20:54:27.237 [main] INFO com.geniu.reactor.TestReactorOrderV2 - onErrorContinue=TestUser(name=)
20:54:27.237 [main] INFO com.geniu.reactor.TestServiceImpl2 - execute.test.service2
service5.threadName=main
20:54:28.246 [main] INFO com.geniu.reactor.TestReactorOrderV2 - onErrorContinue=TestUser(name=)
20:54:28.246 [main] INFO com.geniu.reactor.TestServiceImpl3 - execute.test.service3
service6.threadName=main
TestUser(name=name3)blockFirst 执行耗时ms:2895

  • 1、service1 和 service2 因为返回空,所以继续下一个,最终返回 name3。
  • 2、查看总耗时:2895ms。service1 耗时 500,service2 耗时1000,service3 耗时 1000。发现耗时基本上等于 service1 + service2 + service3 。这是怎么回事呢?查看返回执行的线程,都是 main。

总结:这样实现按照顺序返回第一个正常数据。但是执行并没有异步。下一步:如何实现异步呢?

4 实现异步

4.1 subcribeOn 实现异步

修改 service 实现。增加 .subscribeOn(Schedulers.boundedElastic())

如下:

@Slf4j
public class TestServiceImpl1 implements TestServiceI {
    @Override
    public Mono request() {
        log.info("execute.test.service1");
        return Mono.fromSupplier(() -> {
                    try {
                        System.out.println("service1.threadName=" + Thread.currentThread().getName());
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "";
                })
                //增加subscribeOn
                .subscribeOn(Schedulers.boundedElastic())
                .map(name -> {
                    return new TestUser(name);
                });
    }
}

再次执行输出如下:

21:02:04.213 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
21:02:04.265 [main] INFO com.geniu.reactor.TestServiceImpl1 - execute.test.service1
service4.threadName=boundedElastic-1
21:02:04.300 [main] INFO com.geniu.reactor.TestServiceImpl2 - execute.test.service2
21:02:04.302 [main] INFO com.geniu.reactor.TestServiceImpl3 - execute.test.service3
service2.threadName=boundedElastic-2
service3.threadName=boundedElastic-3
21:02:04.987 [boundedElastic-1] INFO com.geniu.reactor.TestReactorOrderV2 - onErrorContinue=TestUser(name=)
21:02:05.307 [boundedElastic-2] INFO com.geniu.reactor.TestReactorOrderV2 - onErrorContinue=TestUser(name=)
TestUser(name=name6)blockFirst 执行耗时ms:1242

  • 1、发现具体实现 sleep 的线程都不是 main 线程,而是 boundedElastic
  • 2、最终执行耗时 1242ms,只比执行时间最长的 service2 和 service3 耗时 1000ms,多一些。证明是异步了。

4.2 CompletableFuture 实现异步

修改 service 实现,使用 CompletableFuture 执行耗时操作(这里是sleep,具体到项目中可能是外部接口调用,DB 操作等);然后使用 Mono.fromFuture 返回 Mono 对象。

@Slf4j
public class TestServiceImpl1 implements TestServiceI{
    @Override
    public Mono request() {
        log.info("execute.test.service1");
        CompletableFuture<String> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("service1.threadName=" + Thread.currentThread().getName());
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return "testname1";
        });

        return Mono.fromFuture(uCompletableFuture).map(name -> {
            return new TestUser(name);
        });
    }
}

执行返回如下:

21:09:59.465 [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework
21:09:59.510 [main] INFO com.geniu.reactor.TestServiceImpl2 - execute.test.service2
service2.threadName=ForkJoinPool.commonPool-worker-1
21:09:59.526 [main] INFO com.geniu.reactor.TestServiceImpl3 - execute.test.service3
service3.threadName=ForkJoinPool.commonPool-worker-2
21:09:59.526 [main] INFO com.geniu.reactor.TestServiceImpl1 - execute.test.service1 
service1.threadName=ForkJoinPool.commonPool-worker-3
21:10:00.526 [ForkJoinPool.commonPool-worker-1] INFO com.geniu.reactor.TestReactorOrder - onErrorContinue=TestUser(name=)
21:10:00.538 [ForkJoinPool.commonPool-worker-2] INFO com.geniu.reactor.TestReactorOrder - onErrorContinue=TestUser(name=)
TestUser(name=testname1)blockFirst 执行耗时ms:1238

  • 1、耗时操作都是使用 ForkJoinPool 线程池中的线程执行。
  • 2、最终耗时和方法1基本差不多。

到此这篇关于Reactor 多任务并发执行且结果按顺序返回第一个的文章就介绍到这了,更多相关Reactor 多任务执行内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Project Reactor源码解析publishOn使用示例

    目录 功能分析 代码示例 prefetch delayError 源码分析 Flux#publishOn() Flux#subscribe() FluxPublishOn#subscribeOrReturn() FluxPublishOn#onSubscribe() 非融合 FluxPublishOn#onNext() FluxPublishOn#trySchedule() FluxPublishOn#run() FluxPublishOn#runAsync() FluxPublishOn#ch

  • Java中多线程Reactor模式的实现

    目录 1. 主服务器 2.IO请求handler+线程池 3.客户端 多线程Reactor模式旨在分配多个reactor每一个reactor独立拥有一个selector,在网络通信中大体设计为负责连接的主Reactor,其中在主Reactor的run函数中若selector检测到了连接事件的发生则dispatch该事件. 让负责管理连接的Handler处理连接,其中在这个负责连接的Handler处理器中创建子Handler用以处理IO请求.这样一来连接请求与IO请求分开执行提高通道的并发量.同时

  • Reactor反应器的实现方法详解

    大多数应用都会使用ACE_Reactor::instance()提供的默认反应器实例.但是你也可以选择自己的反应器,这是因为ACE使用了Bridge模式(使用两个不同的类:一个是编程接口,另一个是实现,第一个类会把各个操作传给第二个类).例如使用线程池反应器实现:ACE_TP_Reactor* tp_reactor = new ACE_TP_Reactor;ACE_Reactor* my_reactor = new ACE_Reactor(tp_reactor, 1);//1表示my_react

  • Reactor中的onErrorContinue 和 onErrorResume

    目录 前言 1 基础功能 2 只有 onErrorResume () 3 只有 onErrorContinue() 4 onErrorResume() 然后 onErrorContinue() 5 使用 onErrorResume() 模拟 onErrorContinue() 6 使用 onErrorResume() 和下游的 onErrorContinue() 模拟 onErrorContinue() 前言 这似乎是 Reactor 的热门搜索之一,至少当我在谷歌中输入 onErrorCont

  • 如何使用Reactor完成类似Flink的操作

    一.背景 Flink在处理流式任务的时候有很大的优势,其中windows等操作符可以很方便的完成聚合任务,但是Flink是一套独立的服务,业务流程中如果想使用需要将数据发到kafka,用Flink处理完再发到kafka,然后再做业务处理,流程很繁琐. 比如在业务代码中想要实现类似Flink的window按时间批量聚合功能,如果纯手动写代码比较繁琐,使用Flink又太重,这种场景下使用响应式编程RxJava.Reactor等的window.buffer操作符可以很方便的实现. 响应式编程框架也早已

  • Reactor 多任务并发执行且结果按顺序返回第一个

    目录 1 场景 2 创建 service 2.1 创建基本接口和实体类 2.2 创建 service 实现 3 主体方法 4 实现异步 4.1 subcribeOn 实现异步 4.2 CompletableFuture 实现异步 1 场景 调用多个平级服务,按照服务优先级返回第一个有效数据. 具体case:一个页面可能有很多的弹窗,弹窗之间又有优先级.每次只需要返回第一个有数据的弹窗.但是又希望所有弹窗之间的数据获取是异步的.这种场景使用 Reactor 怎么实现呢? 2 创建 service

  • python实现多线程的方式及多条命令并发执行

    一.概念介绍 Thread 是threading模块中最重要的类之一,可以使用它来创建线程.有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法:另一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入. Thread模块是比较底层的模块,Threading模块是对Thread做了一些包装的,可以更加方便的被使用. 另外在工作时,有时需要让多条命令并发的执行, 而不是顺序执行. 二.代码样例 #!/usr/bin/py

  • C#利用Task实现任务超时多任务一起执行的方法

    前言 其实Task跟线程池ThreadPool的功能类似,不过写起来更为简单,直观.代码更简洁了,使用Task来进行操作.可以跟线程一样可以轻松的对执行的方法进行控制. 创建Task有两种方式,一种是使用构造函数创建,另一种是使用 Task.Factory.StartNew 进行创建. 如下代码所示 1.使用构造函数创建Task Task t1 = new Task(MyMethod); 2.使用Task.Factory.StartNew 进行创建Task Task t1 = Task.Fact

  • 使用Python paramiko模块利用多线程实现ssh并发执行操作

    1.paramiko概述 ssh是一个协议,OpenSSH是其中一个开源实现,paramiko是Python的一个库,实现了SSHv2协议(底层使用cryptography). 有了Paramiko以后,我们就可以在Python代码中直接使用SSH协议对远程服务器执行操作,而不是通过ssh命令对远程服务器进行操作. 由于paramiko属于第三方库,所以需要使用如下命令先行安装 2.安装paramiko pip install paramiko 3.常用方法 connect():实现远程服务器的

  • Jmeter并发执行Python 脚本的完整流程

    目录 1. 前言 2. Python 实现文件上传 2-1获取文件信息及切片数目 2-2切片及分段上传 2-3合并文件 2-4文件路径参数化 3. Jmeter 并发执行 4. 最后 本篇文章以文件上传为例,聊聊 Jmeter 并发执行 Python 脚本的完整流程 1. 前言 大家好,我是安果! 最近有小伙伴后台给我留言,说自己用 Django 写了一个大文件上传的 Api 接口,现在想本地检验一下接口并发的稳定性,问我有没有好的方案 本篇文章以文件上传为例,聊聊Jmeter 并发执行 Pyt

  • Java线程池并发执行多个任务方式

    目录 Java线程池并发执行多个任务 Java线程池的正确使用 1.Executors存在什么问题 2.Executors为什么存在缺陷 3. 线程池参数详解 4. 线程池正确打开方式 Java线程池并发执行多个任务 Java在语言层面提供了多线程的支持,线程池能够避免频繁的线程创建和销毁的开销,因此很多时候在项目当中我们是使用的线程池去完成多线程的任务. Java提供了Executors 框架提供了一些基础的组件能够轻松的完成多线程异步的操作,Executors提供了一系列的静态工厂方法能够获

  • Python中执行存储过程及获取存储过程返回值的方法

    本文实例讲述了Python中执行存储过程及获取存储过程返回值的方法.分享给大家供大家参考,具体如下: 在Pathon中如何执行存储过程呢?可以使用如下方法: 存储过程定义基本如下: ALTER procedure [dbo]. [mysp] @Station varchar ( 50), @SN varchar ( 50), @Info varchar ( 500) output , @Msg varchar ( 500) output 1. 使用adodbapi from adodbapi i

  • webBrowser执行js的方法,并返回值,c#后台取值的实现

    实例如下: private void Form1_Load(object sender, EventArgs e) { webBrowser1.Navigate(Application.StartupPath + @"\i.html"); txtInfo.Text = webBrowser1.DocumentText; } private void button2_Click(object sender, EventArgs e) { webBrowser1.Document.Invo

  • 解决mybatis执行SQL语句部分参数返回NULL问题

    今天在写代码的时候发现一个问题:mybatis执行sql语句的时候返回bean的部分属性为null,在数据库中执行该sql语句能够正常返回,把相关代码反反复复翻了个遍,甚至都重启eclipse了,依旧没解决问题,后来网上搜了一下,还真有类似的问题. 闲话少说,直接说问题,该sql语句是自己写的,resultType直接用了该bean全名称,最终导致部分属性显示为null, 原来的写法: <select id="selectByArticle" parametertype=&quo

  • 批量获取memcache值并按key的顺序返回的实现代码

    通过memcached的getMulti函数来批量获取如下15个ID的值. 31639,33878,177410,9735,589,12076,25953,22447,15368,15358,33853,26658,26659,12477,15366 $md->getMulti($arr_id); 返回的顺序: line_31639,line_33878,line_177410,line_9735,line_589,line_12076,line_25953,line_22447,line_15

随机推荐