响应式编程中Mono和Flux的区别

前言

当我们在使用Project Reactor,或者使用依赖于它的框架的时候。例如spring webflux,spring cloud gateway等,经常会用看到代码中有Mono和Flux两个术语。

响应式流

Reactor是由Pivotal公司开发的开源框架,它是开发响应式应用的基础。如今,它是建立在由Netflix、Pivotal和Lightbend的工程师以及其他大型JAVA玩家(Oracle和Red Hat)提出的联合倡议(reactive streams initiative)的基础上。
在这个倡议中,响应式流的规范被创建,有以下几个关键因素:

  • 响应式流应该是非阻塞的,
  • 它是一种数据流,
  • 它是异步工作的,
  • 它需要能够处理背压。

制定以上标准的原因是:当我们通常写应用程序的时候,我们会进行数据库调用、HTTP调用,我们发起请求,阻塞线程,直到有响应返回,然后继续。虽然这种方式可以工作,但是这是一种资源浪费。

发布者Publisher

在开发过程中,不再返回简单的POJO对象,而必须返回其他内容,在结果可用的时候返回。在响应式流的规范中,被称为发布者(Publisher)。主动推送数据给Subscriber(订阅者也可以叫观察者),该方法允许使用者在POJO可用时获取它。如果Publisher发布消息太快,超过了Subscriber的处理速度,如何处理。这时就出现了Backpressure(背压-----指在异步场景中,被观察者发送事件速度远快于观察者的处理速度的情况下,一种告诉上游的被观察者降低发送速度的策略)

发布者可以通过以下两种形式返回结果:

  • Mono返回0个或1个结果,
  • Flux返回0个或多个结果,可能是无限个。

Mono

Mono是Publisher的一种,返回0或者1个结果,也可以返回一个Optional。

Mono最多只触发一个事件,它跟RxJava的Single和Maybe类似,所以可以把Mono<Void>用于在异步任务完成时发出通知。
举个例子,我们有如下代码:

1
2
3
4
public Person findCurrentUser() {
    if (isAuthenticated()) return new Person("Jane", "Doe");
    else return null;
}

在Java8中,我们可以这样写:

1
2
3
4
public Optional<Person> findCurrentUser() {
    if (isAuthenticated()) return Optional.of(new Person("Jane", "Doe"));
    else return Optional.empty();
}

如果我们使用响应式流,可以写成这样:

1
2
3
4
public Mono<Person> findCurrentUser() {
    if (isAuthenticated()) return Mono.just(new Person("Jane", "Doe"));
    else return Mono.empty();
}

Flux

Flux也是Publisher的一种,返回0或者多个结果,甚至可以返回无数个结果。通常将其用作,集合collection、数组array、或者流stream的响应式计数方式。
举个例子,我们有如下代码:

1
2
3
4
5
6
public List<Person> findAll() {
    return Arrays.asList(
        new Person("Jane", "Doe"),
        new Person("John", "Doe")
    );
}

在Java8中,我们可以用Stream来实现:

1
2
3
4
5
6
public Stream<Person> findAll() {
    return Stream.of(
        new Person("Jane", "Doe"),
        new Person("John", "Doe")
    );
}

如果我们使用Flux,可以这样实现:

1
2
3
4
5
6
public Flux<Person> findAll() {
    return Flux.just(
        new Person("Jane", "Doe"),
        new Person("John", "Doe")
    );
}

从上面可以看到,响应式编程,与函数式编程很类似。

订阅者Subscriber

以下代码会在控制台输出什么呢?

1
2
3
4
Flux
    .just(1, 2, 3, 4)
    .reduce(Integer::sum)
    .log();

答案是什么都不会输出,响应式流使用push模型,每一项都按照发布者的速度推送到流上,而不管订阅者是否能够跟随。但是也不用担心,因为有背压(back pressure)的存在,可以保证正确。
上述代码通常会认为输出10,但是其实不是的。因为响应流失延迟的,也可以称为懒惰的,只要没有订阅者就不会启动。所以,订阅者也是必不可少的一部分。

这个跟JAVA8中的惰性求值、及早求值概念类似。

异步特性

发布者在本质上是异步的,然后并非总是异步的,是否异步,取决于发布者的类型。看如下代码:

1
2
3
4
5
6
AtomicInteger sum = new AtomicInteger(0);
Flux
    .just(1, 2, 3, 4)
    .reduce(Integer::sum)
    .subscribe(sum::set);
log.info("Sum is: {}", sum.get());

不同的人可能有不同的答案:1、要么输出10,因为对数字求和;2、要么输出0,因为是异步操作,在执行log的时候,还未进行sum求和。
正确的答案是第一种:输出10。因为Flux.just()默认情况下使用当前线程,因此程序在执行到达日志语句时已经计算出了结果。
那下面这个代码会输出什么吗?

1
2
3
4
5
6
7
AtomicInteger sum = new AtomicInteger(0);
Flux
    .just(1, 2, 3, 4)
    .subscribeOn(Schedulers.elastic())
    .reduce(Integer::sum)
    .subscribe(sum::set);
logger.info("Sum is: {}", sum.get());

将会输出0,因为在这里使用了subscribeOn方法,将使得订阅者在异步线程执行。因此,根据响应流的性质,它可以是同步的,也可以是异步的。上述代码可以将logger打印采用lambda实现:

1
2
3
4
Flux
    .just(1, 2, 3, 4)
    .reduce(Integer::sum)
    .susbcribe(sum -> logger.info("Sum is: {}", sum);

构建自己的流

Project Reactor提供了很多内置的发布者。然而,在某些情况下,我们必须创建自己的Publisher。Mono和Flux都提供了create()方法来构建自定义的流。
例如,我们想使用响应流的Twitter4J库,可以写成这样:

1
2
3
4
5
6
return Flux.create(sink -> {
    TwitterStream twitterStream = new TwitterStreamFactory(configuration).getInstance();
    twitterStream.onStatus(sink::next);
    twitterStream.onException(sink::error);
    sink.onCancel(twitterStream::shutdown);
});

这也是一个无限流的例子,因为推文的数量将永远持续下去(或者直到Twitter关闭)。

热流和冷流

热流称为Host Stream,冷流称为Cold Stream。两者的区别在于:当我们多个订阅者使用冷观察的时候,流将重新启动。热观察的时候,流将复用。默认情况下,流是冷流。
如下方代码,默认采用的是冷流:

1
2
3
4
5
6
7
8
9
Flux<Integer> numbers = Flux
    .just(1, 2, 3, 4)
    .log();
numbers
    .reduce(Integer::sum)
    .subscribe(sum -> logger.info("Sum is: {}", sum));
numbers
    .reduce((a, b) -> a * b)
    .subscribe(product -> logger.info("Product is: {}", product));

上面的例子中,1到4被发布两次,一次针对第一个订阅者,一次针对第二个订阅者。
但是在有些情况下,我们不希望流重头开始。例如HTTP Request,在这种情况下,我们可以使用热流。在Project Reactor中,我们可以使用share()方法(针对Flux)或者cache()方法(针对Mono),代码如下所示:

1
2
3
4
5
6
7
8
9
10
Flux<Integer> numbers = Flux
    .just(1, 2, 3, 4)
    .log()
    .share();
numbers
    .reduce(Integer::sum)
    .subscribe(sum -> logger.info("Sum is: {}", sum));
numbers
    .reduce((a, b) -> a * b)
    .subscribe(product -> logger.info("Product is: {}", product));

通过share()方法,1到4只发布一次,被两个订阅者共享。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WebFlux是Spring框架的一部分,它提供了一种响应编程的方来构建基于非阻塞I/O的应用程序。在WebFlux,我们可以使用两种主要的数据类型:MonoFlux。 1. MonoMono是一种包含零个或一个元素的响应流。它类似于Java 8的Optional,但具有更多的操作符和功能。下面是一些使用Mono的示例: ```java Mono<String> mono = Mono.just("Hello"); // 创建一个包含单个元素的Mono mono.subscribe(System.out::println); // 订阅并打印元素 Mono<Integer> emptyMono = Mono.empty(); // 创建一个空的Mono emptyMono.subscribe(System.out::println); // 不会输出任何内容 Mono<String> errorMono = Mono.error(new RuntimeException("Error")); // 创建一个包含错误的Mono errorMono.subscribe(System.out::println, Throwable::printStackTrace); // 打印错误信息 ``` 2. FluxFlux是一种包含零个或多个元素的响应流。它类似于Java 8的Stream,但具有更多的操作符和功能。下面是一些使用Flux的示例: ```java Flux<String> flux = Flux.just("Hello", "World"); // 创建一个包含多个元素的Flux flux.subscribe(System.out::println); // 订阅并打印元素 Flux<Integer> emptyFlux = Flux.empty(); // 创建一个空的Flux emptyFlux.subscribe(System.out::println); // 不会输出任何内容 Flux<String> errorFlux = Flux.error(new RuntimeException("Error")); // 创建一个包含错误的Flux errorFlux.subscribe(System.out::println, Throwable::printStackTrace); // 打印错误信息 ``` 通过使用MonoFlux,我们可以在WebFlux处理异步和非阻塞的操作,例如处理HTTP请求和响应

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值