Reactor响应式编程系列(七)- 探索Processor
一. Processor简介
Processor
既是一个Publisher
也是一个Subscriber
,是一个接口,其中的两个实现类:
MonoProcessor
FluxProcessor
而FluxProcessor
又可以衍生出多种Processor
,负责应对不同的场景使用(我就只罗列几个比较常见的):
UnicastProcessor
DirectProcessor
EmitterProcessor
接下来就开始进行一一的介绍:
1.1 UnicastProcessor
案例1:
@Test
public void unicastTest() {
UnicastProcessor<Object> processor = UnicastProcessor.create();
FluxSink<Object> sink = processor.sink();
sink.next("aaa");
sink.next("aaa");
sink.next("aaa");
sink.next("aaa");
Flux.just("Hello", "World").subscribe(processor);
processor.subscribe(System.out::println);
// 若打开以下注释,则报错,因为UnicastProcessor只允许有一个订阅者
// processor.subscribe(System.out::println);
sink.next("aaa");
}
运行结果如下:
若打开注释:
UnicastProcessor
可以在多个线程中生产元素,即多个生产源同时生产、下发元素。为了保证元素在多线程的情况下有序的下发,UnicastProcessor
利用SerializedSink
进行控制。
由于UnicastProcessor
继承了FluxProcessor
,因此我们先来看下父类的sink()
方法:
@Deprecated
public final FluxSink<IN> sink() {
return sink(FluxSink.OverflowStrategy.IGNORE);
}
↓↓↓↓↓
@Deprecated
public final FluxSink<IN> sink(FluxSink.OverflowStrategy strategy) {
Objects.requireNonNull(strategy, "strategy");
if (getBufferSize() == Integer.MAX_VALUE){
strategy = FluxSink.OverflowStrategy.IGNORE;
}
FluxCreate.BaseSink<IN> s = FluxCreate.createSink(this, strategy);
onSubscribe(s);
if(s.isCancelled() ||
(isSerialized() && getBufferSize() == Integer.MAX_VALUE)){
return s;
}
// 默认情况下,永远返回True,这样就能得到FluxCreate.SerializedFluxSink
if (serializeAlways())
return new FluxCreate.SerializedFluxSink<>(s);
else
return new FluxCreate.SerializeOnRequestSink<>(s);
}
↓↓↓↓↓ serializeAlways() ↓↓↓↓↓
// 对于UnicastProcessor类来说,它并没有重写下面的方法,也就是沿用父类的方法
protected boolean serializeAlways() {
return true;
}
UnicastProcessor
可以作为订阅者和上游的源产生订阅关系,但是这里有个问题:
如果
UnicastProcessor
与多个分布在不同线程中的上游源产生了订阅关系,那么当有一个生产者元素下发完毕时,其他生产者是否继续下发元素?
来看下UnicastProcessor
类中的几个重要方法:create()
,我们可以发现可以通过自定义的队列来控制背压程度。
@Deprecated
public final class UnicastProcessor<T> extends FluxProcessor<T, T>
implements Fuseable.QueueSubscription<T>, Fuseable, InnerOperator<T, T>,
InternalManySink<T> {
@Deprecated
public static <E> UnicastProcessor<E> create() {
return new UnicastProcessor<>(Queues.<E>unbounded().get());
}
}
subscribe()
方法中可以发现:
static final AtomicIntegerFieldUpdater<UnicastProcessor> ONCE =
AtomicIntegerFieldUpdater.newUpdater(UnicastProcessor.class, "once");
@Override
public void subscribe(CoreSubscriber<? super T> actual) {
Objects.requireNonNull(actual, "subscribe");
// 1.通过原子类操作来控制订阅者的个数
if (once == 0 && ONCE.compareAndSet(this, 0, 1)) {
this.hasDownstream = true;
actual.onSubscribe(this);
this.actual = actual;
if (cancelled) {
this.hasDownstream = false;
} else {
drain(null);
}
}
// 2.当产生第二个订阅关系的时候,会进入else语句
else {
// 3.下发一个错误事件
Operators.error(actual, new IllegalStateException("UnicastProcessor " +
"allows only a single Subscriber"));
}
}
onNext()
方法中可以发现:
// onNext()调用了这个方法,该方法发射的元素流是非空的
@Override
// 代码A
public void emitNext(T value, Sinks.EmitFailureHandler failureHandler) {
if (onOverflow == null) {
// 查看代码C
InternalManySink.super.emitNext(value, failureHandler);
return;
}
InternalManySink.super.emitNext(
value, (signalType, emission) -> {
// 会根据provider的类型来决定返回值
// 若Provider是Sinks.EmitFailureHandler,则返回true,即需要重试
boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_NEXT, emission);
if (!shouldRetry) {
// ...
}
return shouldRetry;
}
);
}
// 代码B
@Override
public EmitResult tryEmitNext(T t) {
// ☆
if (done) {
return EmitResult.FAIL_TERMINATED;
}
if (cancelled) {
return EmitResult.FAIL_CANCELLED;
}
// 可见这里将元素存储到了队列中
if (!queue.offer(t)) {
return (once > 0) ? EmitResult.FAIL_OVERFLOW : EmitResult.FAIL_ZERO_SUBSCRIBER;
}
drain(t);
return EmitResult.OK;
}
// 代码C
interface InternalManySink<T> extends Sinks.Many<T>, ContextHolder {
@Override
// value是要发射出的元素,不能为空;failureHandler是一个失败策略,允许失败重试
default void emitNext(T value, Sinks.EmitFailureHandler failureHandler) {
for (;;) {
// 这里是将元素存储于Queue中,若元素下发结束,则看后面的结果处理switch
Sinks.EmitResult emitResult = tryEmitNext(value);
if (emitResult.isSuccess()) {
return;
}
boolean shouldRetry = failureHandler.onEmitFailure(SignalType.ON_NEXT,
emitResult);
// 若允许失败重试,那么进入循环,直到做出下面的结果处理
if (shouldRetry) {
continue;
}
switch (emitResult) {
// 不作处理,此时没有订阅服务器可以从中获取上下文
case FAIL_ZERO_SUBSCRIBER:
return;
// 放弃该值,并进行错误事件的传播
case FAIL_OVERFLOW:
Operators.onDiscard(value, currentContext());
emitError(Exceptions.failWithOverflow("Backpressure overflow during Sinks.Many#emitNext"),failureHandler);
return;
// 放弃该值
case FAIL_CANCELLED:
Operators.onDiscard(value, currentContext());
return;
// 一般元素下发结束后,会执行扔掉这个值
case FAIL_TERMINATED:
Operators.onNextDropped(value, currentContext());
return;
// 抛异常
case FAIL_NON_SERIALIZED:
throw new EmissionException(emitResult,
"Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially."
);
default:
throw new EmissionException(emitResult, "Unknown emitResult value");
}
}
}
}
在订阅了上游源的情况下,会在上游元素下发完毕的时候调用onComplete()
方法,此时会给done
属性赋值true
。那么此时若UnicastProcessor
作为订阅者和上游不同线程的多个生产者之间存在订阅关系,回到onNext()
方法中的代码B(注释中有☆),可以看到if(done) {return EmitResult.FAIL_TERMINATED;}
,即其他生产者的元素会被直接抛弃。 那么上述的问题也就随之解决了。
// onComplete()最后调用了这个方法
@Override
public EmitResult tryEmitComplete() {
if (done) {
return EmitResult.FAIL_TERMINATED;
}
if (cancelled) {
return EmitResult.FAIL_CANCELLED;
}
done = true;
doTerminate();
drain(null);
return Sinks.EmitResult.OK;
}
总结1:
- 只要有一个生产者元素下发完毕,其他的生产者元素都会被抛弃。
UnicastProcessor
作为发布者,可以在多个线程中同时发布元素,但是只能和一个订阅者建立订阅关系。- 支持背压。
1.2 DirectProcessor
DirectProcessor
的特点如下:
DirectProcessor
可以接收n个Subscriber
。DirectProcessor
不支持背压,其内部也没有用于存储元素的数据结构。DirectProcessor
作为生产者存在的时候,只要对应的订阅者中有一个元素的请求数量为0,就会移除该订阅者,并同时发送一个错误事件。
来看下它相关的onNext()
源码:
// DirectProcessor.DirectInner.tryEmitNext
boolean tryEmitNext(T value) {
if (requested != 0L) {
if (isCancelled()) {
return false;
}
actual.onNext(value);
if (requested != Long.MAX_VALUE) {
REQUESTED.decrementAndGet(this);
}
return true;
}
// 这里可以侧面看出来,只要订阅者的元素请求数量为0,那么就直接返回,在后面会进行移除操作
return false;
}
当DirectProcessor
作为订阅者,并订阅了一个发布源的时候,发布源下发元素结束时会调用DirectProcessor
的onComplete()
方法:
private static final AtomicReferenceFieldUpdater<DirectProcessor, DirectInner[]>SUBSCRIBERS =
AtomicReferenceFieldUpdater.newUpdater(DirectProcessor.class, DirectInner[].class, "subscribers");
@Override
public void onComplete() {
@SuppressWarnings("unused") Sinks.EmitResult emitResult = tryEmitComplete();
}
private EmitResult tryEmitComplete() {
// 1.get之后会执行set方法,将TERMINATED赋值给subscribers
@SuppressWarnings("unchecked")
DirectInner<T>[] inners = SUBSCRIBERS.getAndSet(this, SinkManyBestEffort.TERMINATED);
// 2.而上游源有元素下发的时候,会判断subscribers是否等于TERMINATED
// 若是,则后续接收的元素都会被抛弃掉
if (inners == SinkManyBestEffort.TERMINATED) {
return EmitResult.FAIL_TERMINATED;
}
for (DirectInner<?> s : inners) {
s.emitComplete();
}
return EmitResult.OK;
}
DirectProcessor
作为发布者的时候,当有订阅者订阅时,会先将订阅者包装为DirectInner
对象,来看下它的subscribe()
方法:
@Override
public void subscribe(CoreSubscriber<? super T> actual) {
Objects.requireNonNull(actual, "subscribe");
// 1.先进行兑现规定包装
DirectInner<T> p = new DirectInner<>(actual, this);
actual.onSubscribe(p);
// 2.调用add方法,若已经有订阅者订阅,并且元素下发完毕,即调用了onComplete方法
// 根据上文说法,会将subscribe赋值为 SinkManyBestEffort.TERMINATED
if (add(p)) {
if (p.isCancelled()) {
remove(p);
}
}
// 4.此时该源对于某一个订阅者而言,元素下发完毕,那么过程没报错,则调用actual.onComplete()
// 否则actual.onError(e);
else {
Throwable e = error;
if (e != null) {
actual.onError(e);
}
else {
actual.onComplete();
}
}
}
@Override
public boolean add(DirectInner<T> s) {
DirectInner<T>[] a = subscribers;
// 3.那么此时会返回false
if (a == SinkManyBestEffort.TERMINATED) {
return false;
}
// ...
}
案例2:
@Test
public void directTest() {
DirectProcessor<Object> directProcessor = DirectProcessor.create();
FluxSink<Object> sink = directProcessor.sink();
// 1. 这里的元素是不会输出的,因为此时还并没有订阅者,
sink.next("000");
// 2.订阅后,即可输出发布源发出的元素
directProcessor.subscribe(System.out::println);
sink.next("111");
directProcessor.subscribe(System.out::println);
sink.next("222");
// 上游的源一旦发布元素结束,即调用对应的onComplete方法,将对应的subscribe改为TERMlNATED
// 此时后面的元素都会被抛弃,因此333,444都不会被输出
Flux.just("Hello", "DockerX").subscribe(directProcessor);
sink.next("333");
sink.next("444");
directProcessor.subscribe(System.out::println);
sink.next("555");
}
输出结果如下:
总结2:
DirectProcessor
可以和多个订阅者建立订阅关系。- 只要有一个数据源的元素下发结束,其他数据源的元素就会被抛弃。
- 不支持背压,也无数据存储。
1.3 EmitterProcessor
EmitterProcessor
的特点如下:
EmitterProcessor
可以有多个订阅者,并提供背压支持。EmitterProcessor
可以作为订阅者订阅上游,同步向下发送元素给自己的订阅者。
EmitterProcessor
有一个很特别的点就是:在没有订阅者的时候,它依然可以接受上游源下发的元素。(从案例2我们可以发现,还没有订阅之前,下发的元素是不会输出的)
我们知道,当上游的元素下发完毕之前都没有任何的订阅者进行订阅,那么下发结束后,自然而然的会调用onComplete()
方法,也因此会出现案例2这样的情况,但是对于EmitterProcessor
而言,它能够将元素缓存在自定义的队列中。
同时,EmitterProcessor
还有一个本地变量autoCancel
,其目的是:在EmitterProcessor
的有关订阅者解除订阅,并且订阅关系数为0的时候,自动清除内部的缓存队列,并停止接收新的订阅关系。
来看下相关的源码:
// EmitterProcessor.EmitterInner
@Override
void removeAndDrainParent() {
parent.remove(this);
parent.drain();
}
↓↓↓remove()↓↓↓
final void remove(FluxPublish.PubSubInner<T> inner) {
for (; ; ) {
// ...
if (SUBSCRIBERS.compareAndSet(this, a, b)) {
// 清除队列中的缓存
if (autoCancel && b == EMPTY && Operators.terminate(S, this)) {
// ...
Queue<T> q = queue;
if (q != null) {
q.clear();
}
}
}
return;
}
}
案例3:
@Test
public void emitterTest() {
EmitterProcessor<Object> emitterProcessor = EmitterProcessor.create();
FluxSink<Object> sink = emitterProcessor.sink();
// 1.此时没有订阅者,但是会将这个元素存储到队列中
sink.next("000");
// 2.此时有了订阅者,会将队列中的元素获取到,也因此000可以获取,但是如果用DirectProcessor就不能消费。
// 同时保证该元素不会被重复消费,只有第一个订阅者能够取到
Disposable s1 = emitterProcessor.subscribe(System.out::println);
sink.next("xxx");// 发布了元素xxx,此时订阅者s1已经产生订阅关系,可以输出元素,1条数据
Disposable s2 = emitterProcessor.subscribe(System.out::println);
sink.next("aaa");// 发布了元素aaa,此时订阅者s1和s2已经产生订阅关系,可以输出元素,因此有2条数据
Flux.just("Hello", "DockerX").subscribe(emitterProcessor);// 这里会提示,一个订阅者不能同时订阅两个生产源
sink.next("bbb");// 发布了元素bbb,此时订阅者s1和s2已经产生订阅关系,可以输出元素,因此有2条数据
Disposable s3 = emitterProcessor.subscribe(System.out::println);
sink.next("ccc");// 发布了元素ccc,此时订阅者s1、s2和s3已经产生订阅关系,可以输出元素,因此有3条数据
s1.dispose();// 关闭
s2.dispose();// 关闭
s3.dispose();// 关闭
// 订阅关系已经取消,因此下面的ddd不能够获取到
sink.next("ddd");
}
结果如下:
对于上面注释中提到的报错信息,我们来看下其源码。EmitterProcessor
的sink()
方法用的是父类FluxProcessor
的实现,调用过程中会调用到onSubscribe()
方法,其有自己的实现:
@Override
public void onSubscribe(final Subscription s) {
// setOnce的意思是,只能设置一次,不能设置第二次
// 因此案例3中,我们订阅第二次的时候,就会报错,只不过我的代码不知道为什么打印不出日志,看不到
// 那么为什么案例3中,调用了Flux.just("Hello", "DockerX").subscribe(emitterProcessor)就算二次调用了?
// 因为代码的最开始emitterProcessor.sink();这行代码,内部调用了onSubscribe方法。
// 所以这里就报错了,当然代码还是能正常跑的
if (Operators.setOnce(S, this, s)) {
// ...
queue = Queues.<T>get(prefetch).get();
s.request(Operators.unboundedOrPrefetch(prefetch));
}
}
public static <F> boolean setOnce(AtomicReferenceFieldUpdater<F, Subscription> field, F instance, Subscription s) {
// ...
reportSubscriptionSet();
return false;
}
// Operators
public static void reportSubscriptionSet() {
if (log.isDebugEnabled()) {
// 报错时,会提示如下的信息
log.debug("Duplicate Subscription has been detected",
Exceptions.duplicateOnSubscribeException());
}
}
总结3:
EmitterProcessor
作为源可以有多个订阅者,并且在没有订阅的情况下,可以存储上游的元素到队列中,并且不会被重复消费(被第一个订阅者给获取到),提供背压。EmitterProcessor
在订阅关系数为0的时候,会自动清理队列中的缓存。EmitterProcessor
作为订阅者不能同时订阅两个数据源。
最后,文章到这里也就完了,后面可能会补充ReplayProcessor
这个类,因为这个类有点复杂,所以我没打算在本篇文章叙述(书中对这个类的分析也有几十页,我太懒啦~) 下一篇准备回头看下前几篇文章一直有穿插的一个重要类:QueueSubscription
,看看它到底有着什么样的作用~
感谢大家的观看~○( ^皿^)っHiahiahia…