4.Reactor核心特性

1 篇文章 0 订阅
1 篇文章 0 订阅

这个Reactor项目的主要构件就是 reactor-core,一个反应性的库,它专注于反应流规范和基于Java 8。
Reactor引入了可实现Publisher的可组合反应类型 ,还提供了丰富的操作,尤其是Flux和Mono。Flux对象表示0..N个项目的反应序列。而Mono对象表示(0.. .1)单值。
类型中存在一些语义上的区别,表示一种异步处理粗略基数。举个例子,一个HTTP请求仅产生一个响应,所以做计数操作没有太大意义。因此,将这种HTTP调用的结果表示为Mono比将其表示为Flux更有意义,因为它只涉及零(无)或一(有一个)。
更改处理的最大基数的操作也会切换到相关的类型。举个例子,Flux 中的 count 操作, 返回的却是 Mono。

4.1 Flux 0-N项异步序列

这里写图片描述
Flux是个标准的 Publisher,代表一个0-N发射项异步序列,遇到一个结束信号或一个错误就会终止。因此,Flux有一个值、结束信号、错误3种可能的值。在反应式流规范中,这三种类型的信号转换为对下游对象的onNext、onComplete或onError方法的调用。
在一个大范围的可能信号中,Flux是一个通用型的反应类型。注意,所有事件(甚至终止事件)都是可选的:没有onNext事件,只有onComplete事件表示一个空的有限序列,但是移除onComplete,您将得到一个无限的空序列。同理,无限序列也不一定是空的。例如:Flux.interval(Duration) 产生一个无限的周期性发射的 Flux。

4.2 Mono 0个或1个异步结果

这里写图片描述
Mono是一个特殊的最多发射一个值Publisher,用onComplete 或 onError 来终止。
它仅仅提供了一个对Flux可用的操作子集。例如:组合操作可以忽略右边的发射,返回另一个Mono,也可以从两边发出值。如果两边都发出他们就会被切换成Flux。
举个例子: Mono#concatWith(Publisher)返回一个 Flux ,而 Mono#then(Mono) 返回另一个Mono。
注意,Mono常常用来表示只有完成概念(如Runnable)的无值异步进程。要创建一个,使用一个空的Mono。

4.3 简单的方式创建一个Flux 或 Mono 并订阅它

最简单的方式开始Flux 和Mono是使用他们的众多工厂方法的一个去创建。
例如:创建一个String序列,你可以枚举它们或把它们放入集合中再创建。

Flux<String> seq1 = Flux.just("foo", "bar", "foobar");

List<String> iterable = Arrays.asList("foo", "bar", "foobar");
Flux<String> seq2 = Flux.fromIterable(iterable);

下面是一些其他工厂方法例子:

Mono<String> noData = Mono.empty();    (1)

Mono<String> data = Mono.just("foo");  

Flux<Integer> numbersFromFiveToSeven = Flux.range(5, 3);    (2)

(1) 请注意工厂方法也支持泛型,即使它没有值。
(2) 第一个参数是开始的范围,第二个参数是产生的数量。

说到订阅,Flux 和 Mono 都是利用了Java 8 lambda表达式。您可以广泛地选择将lambdas用于不同的回调组合的.subscribe()方法,如下面的方法签名所示。

Flux 中基于lambda的订阅方法

//订阅并触发一个序列
subscribe(); 

//对每一个生产的值做些事情
subscribe(Consumer<? super T> consumer); 

//处理一个值但也对错误作出反应
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer); 

//处理一个值或一个错误,并在正常结束时执行completeConsumer的代码
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer,
          Runnable completeConsumer); 

//同上,但也可以用subscribe调用时生成的Subscription做些事情
subscribe(Consumer<? super T> consumer,
          Consumer<? super Throwable> errorConsumer,
          Runnable completeConsumer,
          Consumer<? super Subscription> subscriptionConsumer); 

当不再需要数据时,这些方法会返回可以被取消的subscription 的引用。在取消时,源会停止生产值并清理它所创建的任何信息。这种取消和清理行为在Reactor中由通用的 Disposable 接口表示。

4.3.1 subscribe 方法样例

本节包含 subscribe 方法的五个签名中的每个签名的最小示例,下面的方法显示了一个无参方法的例子:

//设置在订阅连接时产生三个值的Flux
Flux<Integer> ints = Flux.range(1, 3); 
// 订阅最简单的方式
ints.subscribe(); 

上面的代码不产生可见输出,但是可以工作。Flux产生3个值。如果我们提供一个lambda表达式,那就可以让这些值显示出来。下面的subscribe 方法例子是可以让值显示出来的一种方式:

Flux<Integer> ints = Flux.range(1, 3); 
ints.subscribe(i -> System.out.println(i));

上面的代码将产生下面的输出

1
2
3

为了演示下一个签名,我们故意引入一个错误,如下例所示:

//设置在订阅连接时产生4个值的Flux
Flux<Integer> ints = Flux.range(1, 4) 
      .map(i -> {  // 我们需要一个Map,以便以不同的方式处理一些值
        if (i <= 3) return i;  // 对大多数的值直接返回
        throw new RuntimeException("Got to 4"); 
      });
      // 订阅一个包含错误处理器的订阅者
ints.subscribe(i -> System.out.println(i), 
      error -> System.err.println("Error: " + error));

现在我们有两个lambda表达式,一个是我们期望的值,一个是错误。上面的代码会产生下面的输出:

1
2
3
Error: java.lang.RuntimeException: Got to 4

下面的subscribe 方法将包含一个错误处理和一个完成事件的处理。

Flux<Integer> ints = Flux.range(1, 4); 
ints.subscribe(i -> System.out.println(i),
    error -> System.err.println("Error " + error),
    () -> {System.out.println("Done");});

错误信号与完成信号都表示终止并且彼此排斥(不能同时存在)。要想完成消费工作,我们必须注意不要触发错误。完成操作对应的是一个没有参数的空括号,因为对应的就是Runnable中的run方法。上面的代码会产生下面的输出:

1
2
3
4
Done

最后一个 subscribe 方法包含一个自定义的 Subscriber ,稍后展示了如何附加自定义Subscriber ,如以下示例所示:

SampleSubscriber<Integer> ss = new SampleSubscriber<Integer>();
Flux<Integer> ints = Flux.range(1, 4);
ints.subscribe(i -> System.out.println(i),
    error -> System.err.println("Error " + error),
    () -> {System.out.println("Done");},
    s -> ss.request(10));
ints.subscribe(ss);

在上面的例子中,我们提供了一个 subscriber 作为 subscribe 方法的参数。下面展示了这个自定义Subscriber 对象, 是最简单的Subscriber实现。

package io.projectreactor.samples;

import org.reactivestreams.Subscription;

import reactor.core.publisher.BaseSubscriber;

public class SampleSubscriber<T> extends BaseSubscriber<T> {

        public void hookOnSubscribe(Subscription subscription) {
                System.out.println("Subscribed");
                request(1);
        }

        public void hookOnNext(T value) {
                System.out.println(value);
                request(1);
        }
}

这个SampleSubscriber 继承至 BaseSubscriber,BaseSubscriber是为用户在Reactor中定义Subscribers 的推荐的抽象类。这个类提供了可以被覆盖以便调整Subscribers 行为的钩子。
默认它会触发一个无限的请求,表现正好与 subscribe() 类似。然而,在你想自定义请求数时继承 BaseSubscriber 是最有用的。

最低限度的实现是重写 hookOnSubscribe(Subscription subscription) 和 hookOnNext(T value)。
在这里,hookOnSubscribe 方法将语句打印到标准输出并发出第一个请求,然后,hookOnNext方法打印一个语句并处理剩余的每个请求,一次一个请求。

SampleSubscriber 将产生下面的输出:

Subscribed
1
2
3
4

[提示]:你当然会重写hookOnError, hookOnCancel, 和 hookOnComplete 方法。你也可能会重写hookFinally 方法。SampleSubscribe是执行有限请求的Subscriber 的最小实现。

反应式流规范还定义了另一种subscribe 方法,它允许你直接使用一个Subscriber 不需要任何其他选项。下面是方法签名:

subscribe(Subscriber<? super T> subscriber);

这个subscribe 方法在你已经有了个 subscriber 时会比较有用,但更常见的是,您需要它,因为您希望在其他回调中执行与订阅相关的操作。最有可能的是,您需要处理背压(backpressure)并自行触发请求。

在这种情况下,您可以使用BaseSubscriber抽象类使事情变得更容易,它在处理背压(backpressure)问题上提供了便利的方法。

使用BaseSubscriber来调整背压

Flux<String> source = someStringSource();

source.map(String::toUpperCase)
      .subscribe(new BaseSubscriber<String>() { 
          @Override
          protected void hookOnSubscribe(Subscription subscription) {
              // BaseSubscriber为各种信号的处理定义了hook, 它还传递了Subscription 对象以便你在其他hook中操作它。
              // request 使背压请求从任何hook传递到Subscription中
              request(1); 
          }

          @Override
          protected void hookOnNext(String value) {
              request(1); //收到新值后,我们会继续逐一请求来源。
          }

          // hookOnComplete, hookOnError, hookOnCancel, hookFinally这些hook会在序列终止时被调用
      });

注意:在操纵请求时,你必须小心生产足够的数据,以使序列向前发展,否则你的 Flux 将被“卡住”。这就是BaseSubscriber迫使您实现 subscription 和 onNext hook的原因,在这里,您通常至少应该调用一次request。

BaseSubscriber 还提供了 requestUnbounded() 方法去切换到无界模式(类似 request(Long.MAX_VALUE))。

4.4 以编程方式创建一个序列

这一节, 我们会通过以编程定义相关事件(onNext, onError, 和 onComplete)的方式来介绍Flux 和 Mono的创建。所有这些方法都有一个共同点,即它们公开了一个API来触发我们称之为 Sink接收器 的事件。实际上有多个Sink接收器 我们马上就会看到。

4.4.1 生成

创建Flux的最简单的方式是用 generate 方法,它需要一个生成器作为参数。
它是同步的逐个释放的,这意味着sink是一个同步接收器,并且它的next()方法最多只能在每次回调调用时调用一次。
您也可以另外调用 error(Throwable) 或 complete(),但这是可选的。
最有用的变量可能是允许您可以保持到Sink的引用的state,以决定接下来要发出什么。生成器函数 BiFunction

Flux<String> flux = Flux.generate(
    () -> 0, // 提供一个初始状态0
    (state, sink) -> {
      sink.next("3 x " + state + " = " + 3*state);  // 我们使用state类确定要要发送什么(3的乘法表中的一行)
      if (state == 10) sink.complete();  // 我们也使用它来确定什么时候停止
      return state + 1; //返回新状态(除非序列终止)
    });

上面的代码将生成3的乘法表序列

3 x 0 = 0
3 x 1 = 3
3 x 2 = 6
3 x 3 = 9
3 x 4 = 12
3 x 5 = 15
3 x 6 = 18
3 x 7 = 21
3 x 8 = 24
3 x 9 = 27
3 x 10 = 30

你也可以使用泛型参数< S >。 可以使用AtomicLong作为state 重写上面的示例。

Flux<String> flux = Flux.generate(
    AtomicLong::new, 
    (state, sink) -> {
      long i = state.getAndIncrement(); 
      sink.next("3 x " + i + " = " + 3*i);
      if (i == 10) sink.complete();
      return state; // 返回同一个实例作为新的状态
    });

如果你的state 对象需要清理,可以使用generate(Supplier < S >, BiFunction, Consumer< S >)重载的方法。

Flux<String> flux = Flux.generate(
    AtomicLong::new,
      (state, sink) -> { 
      long i = state.getAndIncrement(); 
      sink.next("3 x " + i + " = " + 3*i);
      if (i == 10) sink.complete();
      return state; 
    }, (state) -> System.out.println("state: " + state)); 
}

对于包含数据库连接或在处理结束时需要另外处理的其他资源的(state)状态,使用lambda可以关闭连接,或者以其他方式处理进程结束时应该执行的任何任务。

4.4.2 创建

通过编程创建Flux的更高级形式 create 可以工作在同步或异步下,并且适用于每轮多次发射的情况。除了next, error 和 complete 方法,它还暴露了一个 FluxSink 方法。与 generate 不同,它没有基于状态的参数,另一方面,它可以在回调时触发多次事件。

create 对于桥接一个已经存在的API 与 ractive 世界非常有用。比如基于监听器的异步API

假设您使用的是基于侦听器的API。它以块处理数据并有两个事件:(1)数据块已准备就绪,(2)处理完成(终端事件),如MyEventListener接口中所示:

interface MyEventListener<T> {
    void onDataChunk(List<T> chunk);
    void processComplete();
}

你可以使用create 桥接到 Flux

Flux<String> bridge = Flux.create(sink -> {
    myEventProcessor.register( 
      new MyEventListener<String>() { 

        public void onDataChunk(List<String> chunk) {
          for(String s : chunk) {
            sink.next(s); 
          }
        }

        public void processComplete() {
            sink.complete(); 
        }
    });
});

此外,由于create可以是异步的管理背压,您可以通过指定一个OverflowStrategy(溢出策略)来细化背压的行为:

  • IGNORE 完全忽略下游的背压请求。这可能会导致在队列完全处于下游时出现IllegalStateException。
  • ERROR 当下游无法跟上时,会抛出 IllegalStateException。
  • DROP 当下游还没准备好接收时,直接丢弃来的信号。
  • LATEST 让下游仅仅获得流中最新的信号。
  • BUFFER (默认)在下游没有跟上时,会缓存所有信号。(这个缓存没有边界,可能会导致 OutOfMemoryError)

Mono 也有一个 create 生成器 MonoSink, 它不允许多次发射。如果多次发射它会丢弃除第一个信号外的所有信号。

推模式
push方法,它适用于从单个 producer 处理事件。类似Create, push 也可以异步并且可以用create支持的益出策略来管理背压。同一时间只有一个线程可以调用 next, complete, 或 error 方法:

Flux<String> bridge = Flux.push(sink -> {
    myEventProcessor.register(
      new SingleThreadEventListener<String>() { 

        public void onDataChunk(List<String> chunk) {
          for(String s : chunk) {
            sink.next(s); // 使用next将事件从singleThreadlistner推到sink
          }
        }

        public void processComplete() {
            sink.complete(); 
        }

        public void processError(Throwable e) {
            sink.error(e); 
        }
    });
});

混合推拉模式
与push不同,create可以在push或pull模式中使用,使之适合于使用基于侦听器的api进行桥接,在这些api中,数据可以随时异步地交付。FluxSink 中的 onRequest 回调可以被用来跟踪请求。如果需要,回调可用于从源请求更多数据,并仅在请求挂起时将数据交付给sink,从而管理背压。在这种混合模式中,下游可以拉取上游已经准备就绪的数据,上游也可以将准备好的数据推给下游。

Flux<String> bridge = Flux.create(sink -> {
    myMessageProcessor.register(
      new MyMessageListener<String>() {

        public void onMessage(List<String> messages) {
          for(String s : messages) {
            sink.next(s); //异步到达的其余消息也会在稍后被传递
          }
        }
    });
    sink.onRequest(n -> {
        List<String> messages = myMessageProcessor.request(n); //在发出请求时轮询消息。
        for(String s : message) {
           sink.next(s); // 如果消息马上可用,推它们到sink
        }
    });

清理
两个回调,onDispose和onCancel,在取消或终止时执行清理。onDispose 在Flux正常结束、出错、被取消时执行清理。
onCancel可用于在onDispose清除之前执行任何特定于取消的操作。

Flux<String> bridge = Flux.create(sink -> {
    sink.onRequest(n -> channel.poll(n))
        .onCancel(() -> channel.cancel()) //取消
        .onDispose(() -> channel.close())  // 结束、错误、取消时被调用
    });

4.4.3 处理

handle方法有点不同,它存在于Mono 和 Flux 中,是一个实例方法,这意味着它被链接在一个现有的源上。
它接近generate,在这个场景它使用一个SynchronousSink 仅允许一个个发射。然而,handle可以被用来从每个源元素上生成任意的值,可能跳过一些元素。这样,它可以作为map和filter的组合。下面是handle的方法签名:

handle(BiConsumer<T, SynchronousSink<R>>)

让我们思考一个例子:在反应式流规范中不允许null值存在于序列中,如果你想执行一个map动作,但是你想使用一个已经存在的方法作为map函数,这个方法有时会返回null,这时该怎么办呢?
例如,以下方法可以安全地应用于整数源:

public String alphabet(int letterNumber) {
        if (letterNumber < 1 || letterNumber > 26) {
                return null;
        }
        int letterIndexAscii = 'A' + letterNumber - 1;
        return "" + (char) letterIndexAscii;
}

我们可以使用handle 移除nulls 值。

Flux<String> alphabet = Flux.just(-1, 30, 13, 9, 20)
    .handle((i, sink) -> {
        String letter = alphabet(i); 
        if (letter != null) 
            sink.next(letter); 
    });

alphabet.subscribe(System.out::println);

它将输出:

M
I
T

4.5 调度器

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

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

虽然 elastic 可以帮助处理遗留的阻塞代码(如果无法避免的话),但是 single 和 parallel 则不能。因此,在默认的single 或 parallel的Schedules中使用Reactor 阻塞APIs (block(), blockFirst(), blockLast(), toIterable() 和 toStream()) 将导致IllegalStateException 异常。
通过创建实现了NonBlocking 标记接口的线程实例,自定义Schedulers 也可以标记为非阻塞。

此外,您可以通过使用scheduling.fromexecutorservice (ExecutorService)从任何已存在的ExecutorService中创建Scheduler。(您也可以从Executor中创建一个,尽管这样做是不鼓励的)您还可以使用newXXX方法创建各种Scheduler类型的新实例。例如,scheduler.newelastic(yourScheduleName)创建一个新的弹性调度程序,名为yourScheduleName。

未完待续。。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值