Reactor 3 Reference Guide - 选译 (2)

本文介绍了Reactor Core的核心特性,包括如何程序化创建Flux和Mono序列,如同步的generate和异步的create、push方法。详细探讨了线程和Scheduler的使用,如publishOn和subscribeOn。此外,还讨论了错误处理策略,如静态和动态回退值、错误处理operator以及重试机制。文章最后提到了Processor的使用场景和不同类型,如DirectProcessor、UnicastProcessor等。
摘要由CSDN通过智能技术生成

Reactor Core Features

Programmatically creating a sequence

本节介绍如何程序化增加Flux和Mono,以及相关事件(onNext、onError和onComplete)。这些方法都触发了称为sink的事件。实际上是sink的变种。

Synchronous generate

程序化增加Flux的最简单办法是generate方法,接受一个generator 函数。它是同步的,one-by-one 地发射。这是一个SynchronousSink,每次回调最多只能调用一次next()方法。之后可以调用error(Throwable)和complete(),他们是可选的。
最有用的变种可能是:让你保持state,还可以引用sink,决定下一个发射什么。该generator函数变成了BiFunction<S, SynchronousSink< T >, S>,其中< S >就是state对象。你可以为初始state提供一个Supplier< S >,你的generator 函数在每一轮都能返回一个新state。
比如,使用int做state:

Flux<String> flux = Flux.generate(
        //初始state是0
    () -> 0, 
    (state, sink) -> {
        //根据state选择发射的内容
      sink.next("3 x " + state + " = " + 3*state); 
      //完成
      if (state == 10) sink.complete(); 
      //新的state
      return state + 1; 
    });

上面代码的输出是

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

也可以使用可变的。比如下面的代码,使用AtomicLong做state:

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

如果state需要清理资源。可以使用generate(Supplier, BiFunction, Consumer)清理最后一个state实例。
下面的例子就包含了一个Consumer:

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; 
      //最后输出11
    }, (state) -> System.out.println("state: " + state)); 
}

如果该state包含数据库连接,或者其他需要处理的资源。该Consumer表达式可以关闭连接或者其他处理。

Asynchronous & multi-threaded: create

create比较先进,支持每轮多个发射,甚至支持多线程。
它暴露了一个FluxSink,有next、error和complete方法。和generate不同,它没有基于state的变种。在回调中,能触发多线程事件。
create可以把现有API带入响应式世界,比如基于监听器的异步API。
create不会并行化你的代码,也不会把代码变成异步的。即使使用subscribeOn,也要注意:长时间阻塞create lambda(比如无限循环地调用sink.next(t))会锁定管道。请求可能永远不会被执行。使用subscribeOn(Scheduler, false) 变种,requestOnSeparateThread = false,create使用Scheduler线程,数据流执行请求也使用同样的线程。

想像你使用listener-based API。它通过chunks处理事件,有两个事件:(1) 数据准备好,(2) 处理完成(terminal事件)。如MyEventListener接口所示:

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

你可以create一个bridge,放进Flux:

Flux<String> bridge = Flux.create(sink -> {
    //每当myEventProcessor执行时,他们异步执行
    myEventProcessor.register( 
      new MyEventListener<String>() { 

        public void onDataChunk(List<String> chunk) {
          for(String s : chunk) {
              //chunk中的每个元素,成为Flux中的元素
            sink.next(s); 
          }
        }

        public void processComplete() {
            //processComplete事件转换成onComplete
            sink.complete(); 
        }
    });
});

另外,因为create能bridge异步API,管理背压,你可以通过OverflowStrategy,来优化背压:

  • IGNORE:完全忽略下游的背压请求。当下游队列满时,可能产生IllegalStateException
  • ERROR:下游跟不上时,IllegalStateException
  • DROP:下游没准备好接收,就抛弃输入信号
  • LATEST:下游只读取最新信号
  • BUFFER(默认):下游忙不过来就缓冲信号(无限的buffer,可能导致OutOfMemoryError)

Mono也有create generator。MonoSink不允许多个发射。在第一个之后drop全部信号。

Asynchronous but single-threaded: push

push位于generate和create之间,适合处理单个生产者的事件。和create类似,它支持异步,也可以像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) {
              /使用next,从单个监听器线程推送数据给sink
            sink.next(s); 
          }
        }

        public void processComplete() {
            //complete事件由同一个监听器线程生成
            sink.complete(); 
        }

        public void processError(Throwable e) {
            //error事件也由同一个监听器线程生成
            sink.error(e); 
        }
    });
});
An hybrid push/pull model

大多数Reactor operators,比如create,遵循混合push/pull 模式。尽管大部分处理是异步的(建议使用push方式),但是也有pull组件-request。
消费者从源pulls数据,在第一次请求之前不会发射任何数据。然后,只要数据准备好,源就把数据推给消费者,不过要在请求总量范围内。push() 和create()都允许设置一个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.getHistory(n); 
        for(String s : message) {
            //如果消息有效,就推给sink
           sink.next(s); 
        }
    });
});
Cleaning up after push() or create()

两个回调,onDispose和onCancel,在终止或者取消的时候执行清理。onDispose在Flux完成、错误退出或者取消后执行。onCancel在onDispose执行之前执行任何取消动作。

Flux<String> bridge = Flux.create(sink -> {
    sink.onRequest(n -> channel.poll(n))
    //onCancel先被调用,仅适用cancel信号
        .onCancel(() -> channel.cancel())
        //onDispose被调用,适用complete、error或者cancel信号 
        .onDispose(() -> channel.close())  
    });

Handle

handle方法有很大的不同。它是一个实例方法,它是链式的只能在已经存在的源上。支持Mono和Flux。
它接近generate,从某种意义上说,它使用了SynchronousSink,只能一个接一个地发射。不过,handle可以从每个源生成任意值,可能会跳过一些元素。可以当作map和filter的组合。它的签名是:

Flux<R> 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删除任何null:

Flux<String> alphabet = Flux.just(-1, 30, 13, 9, 20)
    .handle((i, sink) -> {
        //映射成字符串
        String letter = alphabet(i); 
        if (letter != null) 
            //如果返回null,不调用sink.next
            sink.next(letter); 
    });

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

程序的输出是

M
I
T

Threading and Schedulers

Reactor是并发不可知论者(concurrency agnostic),它不会强制并发模型。
获取Flux或者Mono不意味着它在专用的线程中运行。大多数operators 会在前一个operator工作的线程中执行。如果不指定,源在subscribe() 调用的线程中运行。

public static void main(String[] args) {
    //Mono<String>在main线程中组装
  final Mono<String> mono = Mono.just("hello "); 

  new Thread(() -> mono
      .map(msg -> msg + "thread ")
      .subscribe(v -> 
      //在Thread-0订阅,于是,map和onNext回调也在该线程中执行
          System.out.println(v + Thread.currentThread().getName()) 
      )
  ).join();

}

程序的输出是

hello thread Thread-0

Reactor中,执行模型和在哪儿执行由使用的Scheduler决定。Scheduler的调度职责类似于ExecutorService,不过作为时钟,做了更多。
Schedulers类的静态方法可以访问执行上下文:

  • immediate() - 当前线程
  • single() - 单个、可重用线程。注意,该方法会为所有调用者重用同一个线程,知道它被disposed。如果你想为每个调用生成专有的线程,使用newSingle()方法
  • elastic() - 一个弹性线程池。按需增加工作线程池,重用空闲的。线程持续空闲太久(默认60秒),会被disposed。I/O阻塞工作可以使用该方法
  • parallel() - 固定大小的线程池:CPU核数

也可以使用Schedulers.fromExecutorService(ExecutorService)方法,从已经存在的ExecutorService增加Scheduler。(也可以从Executor增加)
如果阻塞无法避免,可以使用elastic帮助遗留的阻塞代码,single和parallel不行。所以,如果在默认的single和parallel的Schedulers内使用Reactor的阻塞API(block()、blockFirst()和blockLast(),还有迭代的toIterable()和toStream()),会抛IllegalStateException。
通过创建实现了NonBlocking标记接口的Thread的实例,也能自定义“仅非阻塞”的Schedulers。

一些operators默认使用特定的Scheduler(你也可以使用不同的)。比如,Flux.interval(Duration.ofMillis(300)) 产生的Flux,默认使用Schedulers.parallel()。你也可以这样写:

//使用不同的Scheduler
Flux.interval(Duration.ofMillis(300), Schedulers.newSingle("test"))

Reactor提供了两种在响应式链中切换执行上下文(Scheduler)的方法:publishOn和subscribeOn。都接受Scheduler参数,来切换上下文。但是,在链中publishOn的位置很重要,而subscribeOn的位置却不重要。
当你链式operators的时候,可以包装很多Flux和Mono。一旦你subscribe了,Subscriber对象的链就生成了,一直回到源。当然你看不到,你能看到的外层的Flux(Mono)和Subscription,但是,实际工作发生在这些中间operator的订阅者那里。
有了这些知识,我们仔细看看publishOn和subscribeOn:

publishOn

publishOn跟其他operator一样,在订阅链的中间。从上游接收信号,下游的operators在相关Scheduler的线程上执行。

  • 从Scheduler选择线程,改变执行上下文
  • onNext使用同一个线程,按顺序执行
  • 或者在特定的Scheduler上工作,或者publishOn之后的operators在相同线程上执行
//新Scheduler,包含4个线程
Scheduler s = Schedulers.newParallel("parallel-scheduler", 4); 

final Flux<String> flux = Flux
    .range(1, 2)
    //第一个map在匿名线程上执行
    .map(i -> 10 + i)  
    //切换到s包含的线程
    .publishOn(s)  
    //第二个map在s的线程上执行
    .map(i -> "value " + i);  

//订阅使用了这个匿名线程。print发生在s包含的线程
new Thread(() -> flux.subscribe(System.out::println));

subscribeOn

当构造后向的链时,subscribeOn用于subscription处理。不管你在链的任何位置写subscribeOn,它都影响源的上下文。它不影响调用了publishOn的子序列的上下文。

  • 改变订阅之上整个链的operators的线程
  • 从Scheduler挑一个线程

链中最早的subscribeOn有效。

//新Scheduler,包含4个线程
Scheduler s = Schedulers.newParallel("parallel-scheduler", 4); 

final Flux<String> flux = Flux
    .range(1, 2)
    //第一个map在s的线程上执行
    .map(i -> 10 + i)  
    //因为subscribeOn,从订阅开始切换序列
    .subscribeOn(s)  
    //和第一个map的执行线程相同
    .map(i -> "value " + i);  
//订阅初始化时使用匿名线程。然后马上切换到s的线程
new Thread(() -> flux.subscribe(System.out::println)); 

Handling Errors

响应式系统中,错误是结束事件。当错误发生的时候,它停止序列,传播到链的最后一步,定义的Subscriber的onError方法。
此类错误应该由程序处理。比如,显示错误提示,或者发送有意义的错误信息。因此,应该定义onError方法。
如果没定义,抛出UnsupportedOperationException。你可以做进一步检测,使用Exceptions.isErrorCallbackNotImplemented方法分流。

Reactor也支持在链的中间处理错误,这就是错误operators。

Flux.just(1, 2, 0)
//i等于0时抛异常
    .map(i -> "100 / " + i + " = " + (100 / i)) 
    //错误处理
    .onErrorReturn("Divided by zero :("); 

在你学习错误处理operators前,必须记住,错误是序列的结束事件。甚至你使用了错误处理operator,也不会允许原始(original)序列继续。onError信号会作为新序列的开头(回退)

Error Handling Operators

你可能熟悉使用try-catch处理异常:

  • Catch,返回静态默认值
  • Catch,使用回退(fallback)方法执行其他路径
  • Catch,动态计算fallback值
  • Catch,包装成一个BusinessException,重新抛出
  • Catch,记录错误信息,重新抛出
  • 使用finally块或者try-with-resource清理资源

所有的这些,在Reactor中都有等价物,有对应的错误处理operators。

Flux<String> s = Flux.range(1, 10)
//会泡异常
    .map(v -> doSomethingDangerous(v))
    //如果一切正常,会执行 
    .map(v -> doSecondTransform(v));
s.subscribe(value -> System.out.println("RECEIVED " + value), //如果没失败,成功打印
            error -> System.err.println("CAUGHT " + error) //如果有错误,序列种植,打印失败信息
);

和下面的代码类似:

try {
    for (int i = 1; i < 11; i++) {
        //如果错误就抛异常
        String v1 = doSomethingDangerous(i);
        //正确就执行
        String v2 = doSecondTransform(v1); 
        System.out.println("RECEIVED " + v2);
    }
} catch (Throwable t) {
    //异常就跳转到这里
    System.err.println("CAUGHT " + t); 
}
Static Fallback Value

返回静态默认值,相当于onErrorReturn:

try {
  return doSomethingDangerous(10);
}
catch (Throwable error) {
  return "RECOVERED";
}

变成了:

Flux.just(10)
    .map(this::doSomethingDangerous)
    .onErrorReturn("RECOVERED");

也可以使用任选的Predicate:

Flux.just(10)
    .map(this::doSomethingDangerous)
    //只有在异常消息是boom10的时候,才返回
    .onErrorReturn(e -> e.getMessage().equals("boom10"), "recovered10"); 
Fallback Method

如果不想简单返回默认值,而是有其他的处理数据的路径,可以使用onErrorResume:

String v1;
try {
  v1 = callExternalService("key1");
}
catch (Throwable error) {
  v1 = getFromCache("key1");
}

String v2;
try {
  v2 = callExternalService("key2");
}
catch (Throwable error) {
  v2 = getFromCache("key2");
}

变成了

Flux.just("key1", "key2")
//对于每个key,异步调用外部服务
    .flatMap(k -> callExternalService(k)
    //如果调用失败,返回缓存值 
        .onErrorResume(e -> getFromCache(k)) 
    );

onErrorResume也有Predicate变种:

Flux.just("timeout1", "unknown", "key2")
    .flatMap(k -> callExternalService(k)
        .onErrorResume(error -> { //动态选择
            if (error instanceof TimeoutException) //超时
                return getFromCache(k);
            else if (error instanceof UnknownKeyException)  //不知道的key
                return registerNewEntry(k, "DEFAULT");
            else
                return Flux.error(error); //重新抛出异常
        })
    );
Dynamic Fallback Value

也可以根据异常,计算fallback值。
比如,你返回的MyWrapper类型,专门有一个保存了异常的变种(想一下Future.complete(T success)和Future.completeExceptionally(Throwable error))。

try {
  Value v = erroringMethod();
  return MyWrapper.fromValue(v);
}
catch (Throwable error) {
  return MyWrapper.fromError(error);
}

如果使用onErrorResume,可以这样写:

erroringFlux.onErrorResume(error -> Mono.just( 
        //需要计算异常中的值
        MyWrapper.fromError(error) 
));
Catch and Rethrow

比如

try {
  return callExternalService(k);
}
catch (Throwable error) {
  throw new BusinessException("oops, SLA exceeded", error);
}

可以写成

Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))
    .onErrorResume(original -> Flux.error(
            new BusinessException("oops, SLA exceeded", original))
    );

也可以这样

Flux.just("timeout1")
    .flatMap(k -> callExternalService(k))
    .onErrorMap(original -> new BusinessException("oops, SLA exceeded", original));
Log or React on the Side

如果你希望错误继续传播,不修改序列,只是记录它,可以使用doOnError。

try {
  return callExternalService(k);
}
catch (RuntimeException error) {
  //记录日志
  log("uh oh, falling back, service failed for key " + k);
  throw error;
}

该doOnError,和前面的其他以doOn为前缀的operators一样,有时候被称为“副作用”。可以用来在不修改序列事件的情况下查看他们。
下面的例子还会传播错误,但是我们记录下来了。我们甚至使用了错误统计计数器。

LongAdder failureStat = new LongAdder();
Flux<String> flux =
Flux.just("unknown")
    .flatMap(k -> callExternalService(k) //调用失败
        .doOnError(e -> {
            failureStat.increment();
            log("uh oh, falling back, service failed for key " + k); //日志+统计
        })
        //仍然会因为错误而终止,除非我们在这里使用error-recovery operator
    );
Using Resources and the Finally Block

对于

Stats stats = new Stats();
stats.startTimer();
try {
  doSomethingDangerous();
}
finally {
  stats.stopTimerAndRecordTiming();
}

try (SomeAutoCloseable disposableInstance = new SomeAutoCloseable()) {
  return disposableInstance.toString();
}

分别对应doFinally和using。

不管序列终止(onComplete、onError)或者被取消,都会执行doFinally。你可以知道结束类型:

Stats stats = new Stats();
LongAdder statsCancel = new LongAdder();

Flux<String> flux =
Flux.just("foo", "bar")
    .doOnSubscribe(s -> stats.startTimer())
    .doFinally(type -> { //结束类型SignalType
        stats.stopTimerAndRecordTiming();
        if (type == SignalType.CANCEL) //如果是取消,就统计
          statsCancel.increment();
    })
    .take(1);//发射一个元素就取消

using用于Flux派生自资源的情况,而且处理完成必须处置资源。
让我们使用Disposable替换try-with-resource的AutoCloseable接口:

AtomicBoolean isDisposed = new AtomicBoolean();
Disposable disposableInstance = new Disposable() {
    @Override
    public void dispose() {
        isDisposed.set(true); 
    }

    @Override
    public String toString() {
        return "DISPOSABLE";
    }
};

我们这样使用using():

Flux<String> flux =
Flux.using(
        () -> disposableInstance, //生成资源,返回我们的Disposable
        disposable -> Flux.just(disposable.toString()), //处理资源,返回Flux<T>
        Disposable::dispose //当前面的Flux结束,清理资源
);

订阅、执行完该序列后,isDisposed变成了true。

Demonstrating the Terminal Aspect of onError

为了证明,发生错误的时候,这些operators导致上游的原始序列结束,看下面使用Flux.interval的例子。

Flux<String> flux =
Flux.interval(Duration.ofMillis(250))
    .map(input -> {
        if (input < 3) return "tick " + input;
        throw new RuntimeException("boom");
    })
    .onErrorReturn("Uh oh");

flux.subscribe(System.out::println);
Thread.sleep(2100);

程序每250毫秒打印一行,输出是

tick 0
tick 1
tick 2
Uh oh
Retrying

关于错误处理,还有一个有趣的operator:retry。
它会重新订阅上游的Flux。这实际上是一个不同的序列,源已经结束了。

Flux.interval(Duration.ofMillis(250))
    .map(input -> {
        if (input < 3) return "tick " + input;
        throw new RuntimeException("boom");
    })
    .retry(1)
    .elapsed() //把每个值和上一个值发射以后持续的时间相关联
    .subscribe(System.out::println, System.err::println); 

Thread.sleep(2100);

程序的输出是

259,tick 0
249,tick 1
251,tick 2
506,tick 0 //新的interval,从0开始
248,tick 1
253,tick 2
java.lang.RuntimeException: boom

可以看到,retry(1)只重新订阅了一次interval。在第二次,因为还出错,所以错误被传播到下游,序列结束了。
还有更先进的retry版本(retryWhen),使用一个伙伴Flux,判断特定的错误是否应该重试。这个伙伴Flux由用户装饰(decorated),自定义retry条件。
伙伴Flux是一个Flux,传的参数是Function,返回Publisher<?>。retry这样工作:

  • 错误发生,被传给伙伴Flux
  • 如果伙伴Flux发射一个值,retry发生
  • 如果伙伴Flux完成,retry结束,序列完成
  • 如果伙伴Flux抛异常e,retry结束,序列抛e
Flux<String> flux = Flux
    .<String>error(new IllegalArgumentException()) 
    .doOnError(System.out::println) 
    .retryWhen(companion -> companion.take(3)); //重试3次

上面的程序的结果是一个empty Flux,但是它successfully完成了。因为相同Flux上的retry(3) 以最后的错误结束,这个retryWhen例子和retry(3)不完全一样。
想达到相同的效果,需要一些技巧:

Flux<String> flux =
Flux.<String>error(new IllegalArgumentException())
    .retryWhen(companion -> companion
    .zipWith(Flux.range(1, 4), 
          (error, index) -> { 
            if (index < 4) return index; 
            else throw Exceptions.propagate(error); 
          })
    );

Handling Exceptions in Operators or Functions

通常,所有的operators自身就包含可能抛异常的代码,调用的用户代码也可能产生类似的失败,所以,都包含失败处理。
根据经验,Unchecked Exception总是通过onError传播。比如,在一个map函数内部抛RuntimeException会转换成一个onError事件:

Flux.just("foo")
    .map(s -> { throw new IllegalArgumentException(s); })
    .subscribe(v -> System.out.println("GOT VALUE"),
               e -> System.out.println("ERROR: " + e));

上面代码的输出是

ERROR: java.lang.IllegalArgumentException: foo

Reactor定义了一个异常集合(比如OutOfMemoryError),认为他们是fatal,见Exceptions.throwIfFatal方法。这些错误意味着Reactor不能继续运行,会被抛出。
在内部,也存在unchecked exception不能被传播的情况(尤其在订阅和请求阶段),由于并发竞争(concurrency races)可能同时满足onError和onComplete条件。当这些竞争发生的时候,错误可能不能传播给dropped。不过,仍然可以通过定制钩子(hook)做一定程度的管理。

你可能会问,那Checked Exceptions呢?
比如,你调用了申明了会抛异常的方法,还是应该使用try-catch块处理异常。你有几种选择:

  • 捕获异常,从中恢复。sequence继续
  • 捕获异常,包装成unchecked异常,然后抛出(中断sequence)。Exceptions类会为你提供帮助
  • 如果希望返回Flux(比如使用flatMap),可以把异常包装到错误生成中Flux::return Flux.error(checkedException)(序列终止)

Reactor提供了Exceptions类,你可以使用它,确保只包装checked异常:

  • 必要的时候,使用Exceptions.propagate包装异常。它首先调用throwIfFatal,不会包装RuntimeException
  • 使用Exceptions.unwrap得到未包装的原始异常

比如,下面的方法会抛IOException:

public String convert(int i) throws IOException {
    if (i > 3) {
        throw new IOException("boom " + i);
    }
    return "OK " + i;
}

现在,你想在map中调用该方法。你必须捕获该异常,而且map函数不能再次抛出它,你可以这样:

Flux<String> converted = Flux
    .range(1, 10)
    .map(i -> {
        try { return convert(i); }
        catch (IOException e) { throw Exceptions.propagate(e); }
    });

然后,在订阅的时候,可以得到原始异常:

converted.subscribe(
    v -> System.out.println("RECEIVED: " + v),
    e -> {
        if (Exceptions.unwrap(e) instanceof IOException) {
            System.out.println("Something bad happened with I/O");
        } else {
            System.out.println("Something bad happened");
        }
    }
);

Processors

Processors是特殊的Publisher,而且也是Subscriber。这就是说,你可以subscribe一个Processor(通常,实现了Flux),你也可以调用方法手工注入数据,或者终止它。
有几种类型的Processors,每个都有特殊的语义,在你研究之前,应该问自己几个问题:

Do I Need a Processor?

应该尽量避免使用Processor,他们不容易正确使用,一般用于特殊场合。
如果你认为Processor很适合,就问一下自己,是否尝试过下面两个选择:

  • operator,或者他们的组合是否符合要求
  • 是否可以用 “generator” operator 代替(通常,他们是桥梁API,不是响应式的。提供的sink可以手工填充sequence或者终止它)

Safely Produce from Multiple Threads by Using the Sink Facade

和直接使用Processors相比,更好的办法是调用sink() 方法获取Sink。
FluxProcessor的Sink是多线程producers。比如,对于UnicastProcessor:

UnicastProcessor<Integer> processor = UnicastProcessor.create();
FluxSink<Integer> sink = processor.sink(overflowStrategy);

之后,多个生产者线程可以使用sink并发生成数据:

sink.next(n);

next方法可能溢出,这由Processor的配置决定:

  • 无边界的Processor,自己通过dropping或者buffering处理溢出
  • 有边界的Processor,对于IGNORE策略会阻塞或者旋转(spins),或者定制overflowStrategy

Overview of Available Processors

Reactor自带的Processor大致可分为几类:

  • direct (DirectProcessor、UnicastProcessor):只能使用Sink提供数据
  • synchronous (EmitterProcessor、ReplayProcessor):通过用户交互推送数据,或者通过上游Publisher同步排泄
  • asynchronous (WorkQueueProcessor、TopicProcessor):数据来自用户交互,或者多个上游Publishers。他们更强大,有RingBuffer支持

异步的最复杂,有许多选项。所以,暴露了Builder接口。而其他简单的只有静态工厂方法。

Direct Processor

Direct Processor可以把信号派发给0个或者多个Subscribers。它不能处理背压。如果最少有一个订阅者请求量少于N,而你推送了N条数据,DirectProcessor就发射IllegalStateException。
一旦Processor终止了(一般通过调用它的sink的error(Throwable)或者complete()方法),它允许更多订阅者订阅,但是会立刻重播termination信号。

Unicast Processor

有内部buffer,可以处理背压。但是,最多只能有一个Subscriber。
UnicastProcessor有几个选项,所以有多个工厂方法。比如默认的是unbounded,如果Subscriber还没请求数据,不管你给它多少数据,都会被缓存。
也可以使用自定义的Queue,如果该队列是有边界的,processor可以拒绝推送数据。也可以定义一个回调,当队列满了就调用该回调。

Emitter Processor

Emitter Processor 能够发射给几个订阅者,并尊重每个订阅者的背压。它还可以订阅一个Publisher,同步地中继它的信号。
还没有订阅的时候,根据可配置的bufferSize,它只能接受很少的数据。如果还没有Subscriber消费数据,onNext被阻塞。
第一个订阅者会收到这些缓存的数据。它不会为后来的订阅者重播数据,后来的订阅者只能收到以后的新数据。
默认地。如果全部订阅者都cancelled,它会清除内部缓存,停止接受新的订阅者。可以通过autoCancel参数修改该行为。

Replay Processor

它缓存数据。或者通过 sink(),或者通过上游Publisher。会为后来的订阅者重播数据。
有几种配置:

  • 缓存单个元素(cacheLast)
  • 缓存有限的(create(int)),或者无限的(create())
  • 缓存基于时间的重播窗口(createTimeout(Duration))
  • 大小和时间的组合(createSizeOrTimeout(int, Duration))
Topic Processor

异步的。当使用shared配置(builder()的share(boolean)选项)生成的时候,可以中继多个上游Publishers。
如果你打算并发调用TopicProcessor的onNext、onComplete或者onError方法,或者来自并发的上游Publisher,会强制使用share选项。
它可以有多个Subscribers。它会为每个Subscriber关联一个线程,它会一直运行,知道产生onError或者onComplete信号,或者订阅被取消。订阅者的数量由executor选项决定。
它包含RingBuffer,存储推送的信号。Subscriber线程会保存RingBuffer的指针。
还有autoCancel选项,默认是true,如果源被cancelled,所有的订阅也都被cancelled。

WorkQueue Processor

异步的。当使用shared配置(builder()的share(boolean)选项)生成的时候,可以中继多个上游Publishers。
它不完全遵循Reactive Streams规范,所以比TopicProcessor节省资源。它也基于RingBuffer,但是不会为每个订阅者增加线程。所以,它比TopicProcessor容易扩展。
来自每个订阅者的请求被集中到一起,被中继的信号只发送给一个Subscriber,采用round-robin模式。
它的选项和TopicProcessor相同。
它不应该有太多的订阅者。否则可能lock该Processor。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值