webflux之reactor-backpressure

终于进入到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);
        });
    }
在进行分析之前,先来回顾一下相关的重要知识点。
  1. 根据reactor的相关规则,基本的调用次序如下
    在这里插入图片描述

  2. 因为只有生产和消费处于不同的线程才可能产生背压,否则生产和消费在同一线程的情况下,就仅仅是同步执行了。这里为了产生不同的线程来完成不同的任务,引入了publishOn。publishOn接受上游的消息,并通过引入新线程,在新线程中将得到的消息供下游订阅者使用。

  3. 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相关的流程图,帮助理解
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值