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。