终于进入到backpressure这个话题了。
这是ReactiveStreams中重中之重的一个理念。在下游的消费速度跟不上上游的生产速度的时候,可以有自动的机制来处理这种情况。使得下游可以根据可以机动的改变请求上游消息事件的数量,从而减缓下游的压力。在使用背压的过程中,可以选择对上游消息事件进行缓存或者是忽略或者是分批处理。这里我们将先介绍一个简单的例子来认识背压。
Demo2
代码1
private void sleepMillis(int num){
try {
Thread.sleep(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void demo6(){
Scheduler sed = Schedulers.newParallel("parallel-scheduler", 4);
//使用Flux.create将支持背压处理。默认的处理方式为
//FluxSink.OverflowStrategy.BUFFER 即所有没有及时处理的消息,将先缓存起来,
//当新的request请求到达的时候,会从缓存中取出消息再处理
Flux<String> bridge = Flux.create(sink -> {
for (int i = 0; i < 1_000_000_000; i++) {
if(i % 100000 == 0)
System.out.println(i);
sink.next("value "+i);
}
}
// ,FluxSink.OverflowStrategy.LATEST
);
bridge
.publishOn(sed,2) //引入并发处理
//接到消息后执行的Consumer实现体
.subscribe(s -> {
System.out.println(Thread.currentThread().getName()+" "+s);
sleepMillis(60);
});
}
在进行分析之前,先来回顾一下相关的重要知识点。
-
根据reactor的相关规则,基本的调用次序如下
-
因为只有生产和消费处于不同的线程才可能产生背压,否则生产和消费在同一线程的情况下,就仅仅是同步执行了。这里为了产生不同的线程来完成不同的任务,引入了publishOn。publishOn接受上游的消息,并通过引入新线程,在新线程中将得到的消息供下游订阅者使用。
-
demo中出现对消息流进行复合处理的情况的时候,就会在流处理上出现多个订阅者。其中onSubscribe方法的调用是从上至下的,反过来request的调用是从下至上的。
下面进行核心源码的分析。
在整个demo的运作中,是分为主线程和scheduler线程在同步执行。由于采用的默认背压机制是缓存机制,主线程将不断的往FluxSink里面塞入发送出来的消息。同时scheduler线程将会根据每次request(2)的请求,执行Consumer的实现体,执行完成后会再次发送request(2)的请求。如此多个线程之前循环调用。其中比较重要的有BufferAsyncSink类,PublishOnSubscriber类中的一些方法。下面将逐个分析。
BufferAsyncSink类中相关方法
代码2
/**
这里将包括BaseSink和BufferAsyncSink中的方法,他们是继承关系
这里的BufferAsyncSink将实现缓存的背压机制,将所有的进来的消息先保存在queue中
要想真正的对下游调用onNext方法就必须要满足2个条件:
a。queue中要有消息
b。requested值要大于0
*/
volatile long requested;
//这里使用的Atomic相关的原子操作,保证在多线程下的操作安全
@SuppressWarnings("rawtypes")
static final AtomicLongFieldUpdater<BaseSink> REQUESTED =
AtomicLongFieldUpdater.newUpdater(BaseSink.class, "requested");
final Queue<T> queue;
@Override
public final void request(long n) {
if (Operators.validate(n)) {
//每当从scheduler线程调用这个方法时,将会对requested值添加2
Operators.addCap(REQUESTED, this, n);
LongConsumer consumer = requestConsumer;
if (n > 0 && consumer != null && !isCancelled()) {
consumer.accept(n);
}
//这里调用的是BufferAsyncSink中的实现,最终调用的是drain()
onRequestedFromDownstream();
}
}
/**
这个方法发生在Flux.create中sink.next("value "+i)之后(见代码1)
*/
@Override
public FluxSink<T> next(T t) {
//在scheduler线程进行Consumer的消费时,这里将不断的对queue进行添加消息
queue.offer(t);
//对sink里面的queuq执行drain操作,既排水操作
drain();
return this;
}
/**
核心方法
*/
void drain() {
//这里判断是否有处理中的事件,一般除非是调用了cancel(),都不会进入if中
if (WIP.getAndIncrement(this) != 0) {
return;
}
//这里的wip和missed的初始化值在正常的情况下都为1,为后续退出循环做了铺垫
//在非正常的情况下,requested可能当前为0,那么上面的if语句会导致wip值一直增值
int missed = 1;
//这里的a是PublishOnSubscriber
final Subscriber<? super T> a = actual;
//这里的q是队列queue
final Queue<T> q = queue;
for (; ; ) {
long r = requested;
long e = 0L;
//只有当requested不为0,即大于0的时候才会进入while中
while (e != r) {
//如果已经调用取消,则情况队列中的消息
if (isCancelled()) {
q.clear();
return;
}
//这里只有调用了complete或者error方法时才会设置为true
boolean d = done;
//这里从队列中获取消息。如果消息为空后续会有相应的处理逻辑
T o = q.poll();
//如果消息为空则empty为true
boolean empty = o == null;
//如果已经完成并且消息为空,则进入if中
if (d && empty) {
Throwable ex = error;
if (ex != null) {
super.error(ex);
}
else {
super.complete();
}
return;
}
//如果是empty则退出当前while循环
if (empty) {
break;
}
//调用PublishOnSubscriber的onNext方法
a.onNext(o);
//累计e
e++;
}
if (e == r) {
//如果已经取消,清空q,返回
if (isCancelled()) {
q.clear();
return;
}
//如果q为空了,执行一段和上方逻辑一样的代码
boolean d = done;
boolean empty = q.isEmpty();
if (d && empty) {
Throwable ex = error;
if (ex != null) {
super.error(ex);
}
else {
super.complete();
}
return;
}
}
//如果e不为0,线程安全的执行e的递减
if (e != 0) {
Operators.produced(REQUESTED, this, e);
}
//对wip字段执行负增值,增值为-missed
//在正常情况下wip会变为0,missed也会设置为0,就会满足退出循环的条件
//对应非正常的情况,这里会一直循环递减,并且每次进行递减的missed值都会大于wip增加的值,为了是最终使得missed为0
missed = WIP.addAndGet(this, -missed);
//如果missed为0了,则退出for循环
if (missed == 0) {
break;
}
}
}
PublishOnSubscriber类中相关方法
代码3
/**
这个方法从代码2中进行调用。代码2中有注释
*/
@Override
public void onNext(T t) {
if (sourceMode == ASYNC) {
trySchedule(this, null, null /* t always null */);
return;
}
if (done) {
Operators.onNextDropped(t, actual.currentContext());
return;
}
//在这一步将消息传入
if (!queue.offer(t)) {
error = Operators.onOperatorError(s,
Exceptions.failWithOverflow(Exceptions.BACKPRESSURE_ERROR_QUEUE_FULL),
t, actual.currentContext());
done = true;
}
//将通过scheduler线程来执行下一步的OnNext和request操作
trySchedule(this, null, t);
}
/**
这个方法是在scheduler线程中最终调用的方法
在这个方法里面会执行OnNext操作和再一次的request操作
*/
void runAsync() {
int missed = 1;
//这里的a是LambdaSubscriber
final Subscriber<? super T> a = actual;
final Queue<T> q = queue;
//这里produced初始值为0
long e = produced;
for (; ; ) {
//这里的requested是在demo第一次调用subscriber的整个调用链中赋值的,值是从LambdaSubscriber中请求的Long.max值。
long r = requested;
while (e != r) {
boolean d = done;
T v;
try {
//获取队列中的消息
v = q.poll();
}
catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
s.cancel();
q.clear();
doError(a, Operators.onOperatorError(ex, actual.currentContext()));
return;
}
boolean empty = v == null;
//检查是否处于终结状态,这里不展开
if (checkTerminated(d, empty, a)) {
return;
}
//如果是empty则退出循环
if (empty) {
break;
}
//调用LambdaSubscriber的onNext方法,将会执行最终的消费现实体中的代码
a.onNext(v);
//累计e
e++;
//如果e达到限制,这里demo中limit为2
if (e == limit) {
//本例是Long.MAX_VALUE,所以不会进入if中
if (r != Long.MAX_VALUE) {
r = REQUESTED.addAndGet(this, -e);
}
//这里将会调用BufferAsyncSink中的request方法,既是代码2中的request方法
//这里就将scheduler线程和main线程都关联起来了,形成了一个互相作用的系统
s.request(e);
e = 0L;
}
}
if (e == r && checkTerminated(done, q.isEmpty(), a)) {
return;
}
//这段代码是和代码2中的类似,也是为了平衡missed和wip的值,最终使得2个值相同,
//并且missed值为0,代表错过的消息数量为0,从而退出循环
int w = wip;
if (missed == w) {
produced = e;
missed = WIP.addAndGet(this, -missed);
if (missed == 0) {
break;
}
}
else {
missed = w;
}
}
}
最后附上代码2和代码3相关的流程图,帮助理解