再临SpringBoot——Spring Reactor Web

什么是Mono/Flux

反应式编程框架主要采用了观察者模式,而SpringReactor的核心则是对观察者模式的一种衍生。
理解Mono和Flux,也可以理解为Publisher(发布者也可以理解为被观察者)主动推送数据给Subscriber(订阅者也可以叫观察者),如果Publisher发布消息太快,超过了Subscriber的处理速度,如何处理。这时就出现了Backpressure。
这就和上一文再临SpringBoot——Reactive Stream联系了起来。

再来具体理解,在Reactor中,经常使用的类并不多,主要有以下两个:

  • Mono 实现了 org.reactivestreams.Publisher 接口,代表0到1个元素的发布者。(可以为0,类似于Optional)
  • Flux 同样实现了 org.reactivestreams.Publisher 接口,代表0到N个元素的发布者。(类似Stream)

看看下面一段代码:

public class Test {
    public static void main(String[] args) {
        Subscriber subscriber=new Subscriber() {
            ...
        };

        String[] strs={"1","2","3"};

        //java8 stream
        Flux.fromArray(strs).map(s->Integer.parseInt(s))
        //java9 reactive stream
                .subscribe(subscriber);

    }
}

其实 Mono/Flux就是java8的stream加上java9的reactive stream的实现,是一个发布订阅模式的衍生。

下面这段代码:

@RestController
@Slf4j
public class HomeController {

    @GetMapping("/get1")
    public String get1(){
        return "get1";
    }

    @GetMapping("/get2")
    public Mono<String> get2() {
        return Mono.just("get2");
    }

    @GetMapping("/get3")
    public String get3() throws InterruptedException {
        log.info("get3 start");
        String result=createStr();
        log.info("get3 end");
        return result;
    }

    @GetMapping("/get4")
    public Mono<String> get4(){
        log.info("get4 start");
        Mono<String> result= Mono.fromSupplier(()-> {
            try {
                return createStr();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "error";
        });
        log.info("get4 end");
        return result;
    }

    private String createStr() throws InterruptedException {
        TimeUnit.SECONDS.sleep(5);
        return "get_sleep5";
    }
}

get1和get2在直观上没有什么不同,如果改成get3和get4这样,接一个比较耗时的方法。我们看看运行结果:

get3:
2019-09-26 16:36:10.234  INFO 8900 --- [ctor-http-nio-2] c.l.demo.controller.HomeController       : get3 start
2019-09-26 16:36:15.239  INFO 8900 --- [ctor-http-nio-2] c.l.demo.controller.HomeController       : get3 end

get4:
2019-09-26 16:37:58.941  INFO 8900 --- [ctor-http-nio-2] c.l.demo.controller.HomeController       : get4 start
2019-09-26 16:37:58.943  INFO 8900 --- [ctor-http-nio-2] c.l.demo.controller.HomeController       : get4 end

直接观察可以看到,get3在HomeController中,占用了5秒的时间。而get4几乎是立即完成了。(注意:在前端的感受都是5秒)。
所以,使用Mono/Flux能够极大的增加后端接受请求的数目。

    @GetMapping(value = "/flux", produces = "text/event-stream")
    public Flux<String> flux() {
        Flux<String> flux = Flux.fromStream(
                IntStream.range(1, 5)
                        .mapToObj(i->{
                            try {
                                TimeUnit.SECONDS.sleep(1);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            return "flux data -- "+i;
                        }));
        return flux;
    }

上面这段代码通过produces = "text/event-stream"能验证后端像流一样,依次返回数据,而不是统一处理后一起返回。
在这里插入图片描述

更多知识,可以查看Reactor核心特性

调度器

与RxJava一样,Reactor 也可以被视为与并发无关的,也就是说,它不强制执行并发模型。相反,它让开发人员掌握主动权。你可以使用一些库帮你实现并发性。在Reactor中,执行模型和执行发生的位置由使用的Scheduler决定的,Scheduler是一个可以有广泛实现的接口。Schedulers 类具有静态方法,可以访问以下执行上下文:

  • 当前线程 (Schedulers.immediate()).
  • 一个单例,可以重复使用的线程 (Schedulers.single()). 注意,此方法对所有调用者重用相同的线程,直到scheduler被处理掉。如果你想每次调用都有一个专用的线程可以使用 Schedulers.newSingle()
  • 一个弹性的线程池 (Schedulers.elastic())。其中工作线程在空闲一段时间(默认60s)后会被回收。对于I/O阻塞工作来说,这是一个不错的选择。Schedulers.elastic() 是一个为阻塞操作提供专用线程的便利的方式,这样它就不会去占用其他资源。【怎么去包装一个同步阻塞调用】。
  • 一个可以并行工作的固定的线程池 (Schedulers.parallel()),它会根据你的CPU核心数来创建线程数量。

先来看看下面这个代码:

public class Test {
    public static void main(String[] args) {
        println("hello world");

        Flux.just("A","B","C")
                .subscribe(Test::println);
    }

    public static void println(Object object){
        String processor=Thread.currentThread().getName();
        System.out.println("[当前线程: "+processor+"]   "+object);
    }
}

Flux.just就是一次处理ABC三个字符串,因为Flux没有forEach方法,所以使用subscribe 进行数据发布。
其输出为:

[当前线程: main]   hello world
16:25:26.235 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
[当前线程: main]   A
[当前线程: main]   B
[当前线程: main]   C

可以看到,目前都是在主线程中跑的。

Flux.just("A","B","C")//发布A->B->C
     	.publishOn(Schedulers.elastic())//线程切换
     	.subscribe(Test::println);

输出:
[当前线程: main]   hello world
16:56:59.199 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
[当前线程: elastic-2]   A
[当前线程: elastic-2]   B
[当前线程: elastic-2]   C

再来看下面这一段:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        println("hello world");

        Flux.just("A","B","C")//发布A->B->C
                .publishOn(Schedulers.elastic())//线程切换
                .map(value->"+"+value)
                .subscribe(Test::println,//数据消费
                        Test::println,//异常处理
                        ()->println("完成操作"));//完成回调

        Thread.sleep(1000L);
    }

    public static void println(Object object){
        String processor=Thread.currentThread().getName();
        System.out.println("[当前线程: "+processor+"]   "+object);
    }
}

输出:
[当前线程: main]   hello world
17:02:16.915 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
[当前线程: elastic-2]   +A
[当前线程: elastic-2]   +B
[当前线程: elastic-2]   +C
[当前线程: elastic-2]   完成操作

我在主线程中设置了sleep方法,因为主线程还没有退出,所以有一个完成操作的回调。如果没有sleep方法,那么主线程在elastic-2线程之前就退出了,可能不会显示完成操作的输出。

下面这个例子更好的诠释了,Flux是java8的stream和java9的reactive stream的组合:

public class Test {
    public static void main(String[] args) {
        println("hello world");

        Flux.just("A", "B", "C")//发布A->B->C
                .publishOn(Schedulers.elastic())//线程切换
                .map(value -> "+" + value)
                .subscribe(Test::println,//数据消费 =OnNext()
                        Test::println,//异常处理 =OnError(Throwable)
                        () -> println("完成操作"),//完成回调 =OnComplete
                        subscription -> {//背压操作 =OnSubscribe(Subscription)
                            subscription.request(2);
                            subscription.cancel();
                        }
                )
        ;

//        Thread.sleep(1000L);
    }

    public static void println(Object object) {
        String processor = Thread.currentThread().getName();
        System.out.println("[当前线程: " + processor + "]   " + object);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值