写作时间:2019-11-30
Spring Boot: 2.2.1 ,JDK: 1.8, IDE: IntelliJ IDEA
1. 说明
此篇从代码层面对响应式Reactor的实战,包括源代码的解析。
2. 核心概念
先看看Reactor的主要角色图:
Operators - Publisher(生成者) / Subscriber(订阅者) 通过push的方式交互
- Nothing Happens Until You
subscribe(),
必须调用subscribe()
方法才会执行。 -
Flux [ 0..N ]
执行序列 -onNext()
,onComplete()
,onError()
-
Mono [ 0..1 ]
-onNext()
,onComplete()
,onError()
Backpressure 反压力:表示上游的生产者可以生产多个产品,订阅者可以根据自身处理能力慢条斯理按个执行。
4. Subscription
订阅
5. onRequest()
请求, onCancel()
取消, onDispose()
处理
线程调度Schedulers
6. 单个线程:immediate()
在当前的线程数执行 /
可复用的线程single()
/ 独占,
新起的线程newSingle()
7. 线程池:
缓存的线程池elastic()
,线程 60秒后被回收/parallel()
可复用的线程池,会创建跟CPU核数匹配的线程,线程不会被回收 /newParallel()
可以独占,新起线程池
错误处理
8. onError相当于try{} catch{}
/
onErrorReturn 遇到异常的时候返回默认值 /
onErrorResume 用一段Lambda闭包处理异常逻辑
9. doOnError 异常处理 / doFinally 表示正常、异常都会处理的逻辑。
3. 工程建立
参照教程【SpringBoot 2.1 | 第一篇:构建第一个SpringBoot工程】新建一个Spring Boot项目,名字叫RPCClient, 在目录src/main/java/resources
下找到配置文件application.properties
,重命名为application.yml
。
Spring Boot 版本选择2.2.1,依赖勾选Developer Tools > Lombok.
pom.xml添加reactor的依赖
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
4. 在主线程调用
package com.zgpeace.reactor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
@SpringBootApplication
@Slf4j
public class ReactorApplication implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ReactorApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
fluxInMainThread();
return;
}
private void fluxInMainThread() throws InterruptedException {
Flux.range(1, 6)
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.doOnComplete(() -> log.info("Publisher Complete 1"))
//.publishOn(Schedulers.elastic())
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
//return 10 / (i - 3);
return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
//.subscribeOn(Schedulers.single())
//.onErrorResume(e -> {
// log.error("Exception {}", e.toString());
// return Mono.just(-1);
//})
//.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete")
//s -> s.request(4)
);
Thread.sleep(2000);
}
}
运行结果
[ main] com.zgpeace.reactor.ReactorApplication : Request 9223372036854775807 number
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 1
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 1
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 2
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 2
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 3
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 3
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 4
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 4
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 5
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 5
[ main] com.zgpeace.reactor.ReactorApplication : Publish Thread[main,5,main], 6
[ main] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[main,5,main]: 6
[ main] com.zgpeace.reactor.ReactorApplication : Publisher Complete 1
[ main] com.zgpeace.reactor.ReactorApplication : Publisher Complete 2
[ main] com.zgpeace.reactor.ReactorApplication : Subscriber Complete
代码解析:
-
Request 9223372036854775807 number
表示请求数据为long的最大值,表示不管生产多少,订阅者都要消费掉 - 发现Publish生产者和Subscribe消费者都在主线程执行
- 执行结束以后Publisher执行Complete逻辑(可以有多个),Subscriber执行Complete逻辑
5. 生产者、消费者在不同的线程池
生产者在线程池Schedulers.elastic()
执行
消费者在复用线程Schedulers.single()
执行
@Override
public void run(ApplicationArguments args) throws Exception {
//fluxInMainThread();
fluxInDifferentThread();
return;
}
private void fluxInDifferentThread() throws InterruptedException {
Flux.range(1, 6)
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.doOnComplete(() -> log.info("Publisher Complete 1"))
.publishOn(Schedulers.elastic())
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
//return 10 / (i - 3);
return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
.subscribeOn(Schedulers.single())
//.onErrorResume(e -> {
// log.error("Exception {}", e.toString());
// return Mono.just(-1);
//})
//.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete")
//s -> s.request(4)
);
Thread.sleep(2000);
}
运行结果:
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 256 number
[ single-1] com.zgpeace.reactor.ReactorApplication : Publisher Complete 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 4
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 4
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 5
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 5
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 6
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 6
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publisher Complete 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscriber Complete
解析:
-
[ single-1] Request 256 number
在single申请请求数为256(以前是在主线程申请) -
[ elastic-2]
后面的生产者消费者都从线程池elastic-2
中获取线程。
6. 异常处理返回默认值
代码实现
@Override
public void run(ApplicationArguments args) throws Exception {
//fluxInMainThread();
//fluxInDifferentThread();
fluxOnErrorReturn();
return;
}
private void fluxOnErrorReturn() throws InterruptedException {
Flux.range(1, 6)
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.doOnComplete(() -> log.info("Publisher Complete 1"))
.publishOn(Schedulers.elastic())
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
return 10 / (i - 3);
//return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
.subscribeOn(Schedulers.single())
//.onErrorResume(e -> {
// log.error("Exception {}", e.toString());
// return Mono.just(-1);
//})
.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete")
//s -> s.request(4)
);
Thread.sleep(2000);
}
解析:
当有异常发生时候,返回-1, .onErrorReturn(-1)
运行结果:
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 256 number
[ single-1] com.zgpeace.reactor.ReactorApplication : Publisher Complete 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -5
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -10
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscriber Complete
解析:
- 当执行到3的时候,除数为0,发生异常,返回-1.
- 因为是异常退出,所以没有执行完成,
Publisher Complete 2
没有执行。 - 流程已经结束,订阅者执行完成
Subscriber Complete
7. 异常发生处理闭包逻辑
@Override
public void run(ApplicationArguments args) throws Exception {
//fluxInMainThread();
//fluxInDifferentThread();
//fluxOnErrorReturn();
fluxOnErrorResume();
return;
}
private void fluxOnErrorResume() throws InterruptedException {
Flux.range(1, 6)
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.doOnComplete(() -> log.info("Publisher Complete 1"))
.publishOn(Schedulers.elastic())
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
return 10 / (i - 3);
//return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
.subscribeOn(Schedulers.single())
.onErrorResume(e -> {
log.error("Exception {}", e.toString());
return Mono.just(-1);
})
//.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete")
//s -> s.request(4)
);
Thread.sleep(2000);
}
执行结果:
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 256 number
[ single-1] com.zgpeace.reactor.ReactorApplication : Publisher Complete 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -5
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -10
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Exception java.lang.ArithmeticException: / by zero
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscriber Complete
解析:
打印异常信息为除数为0, Exception java.lang.ArithmeticException: / by zero
8. 消费者只消费部分生产者产品
这里把消费者设置为4,而生产还是6。
并把.publishOn(Schedulers.elastic())
挪到最前面执行。
@Override
public void run(ApplicationArguments args) throws Exception {
//fluxInMainThread();
//fluxInDifferentThread();
//fluxOnErrorReturn();
//fluxOnErrorResume();
fluxWithSubscribe();
return;
}
private void fluxWithSubscribe() throws InterruptedException {
Flux.range(1, 6)
.publishOn(Schedulers.elastic())
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.doOnComplete(() -> log.info("Publisher Complete 1"))
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
//return 10 / (i - 3);
return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
.subscribeOn(Schedulers.single())
.onErrorResume(e -> {
log.error("Exception {}", e.toString());
return Mono.just(-1);
})
//.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete"),
s -> s.request(4)
);
Thread.sleep(2000);
}
运行结果
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 4 number
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 4
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 4
解析:
-
Request 4 number
生产者只会请求4个线程。 - 这里
Publisher Complete 1
,Publish Complete 2
,Subscriber Complete
都没有打印, 因为上面逻辑都在线程[elastic-2,5,main]
完毕的时候处理,目前都没有结束。
9. 消费者只消费部分生产者产品(并调换线程位置)
public void run(ApplicationArguments args) throws Exception {
//fluxInMainThread();
//fluxInDifferentThread();
//fluxOnErrorReturn();
//fluxOnErrorResume();
//fluxWithSubscribe();
fluxWithSubscribeAndChangePublishOnThreadOrder();
return;
}
private void fluxWithSubscribeAndChangePublishOnThreadOrder() throws InterruptedException {
Flux.range(1, 6)
.doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
.publishOn(Schedulers.elastic())
.doOnComplete(() -> log.info("Publisher Complete 1"))
.map( i -> {
log.info("Publish {}, {}", Thread.currentThread(), i);
//return 10 / (i - 3);
return i;
}
)
.doOnComplete( () -> log.info("Publisher Complete 2"))
.subscribeOn(Schedulers.single())
.onErrorResume(e -> {
log.error("Exception {}", e.toString());
return Mono.just(-1);
})
//.onErrorReturn(-1)
.subscribe( i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
e -> log.error("error {}", e.toString()),
() -> log.info("Subscriber Complete"),
s -> s.request(4)
);
Thread.sleep(2000);
}
结果打印:
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 256 number
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 4
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 4
解析:Request 256 number
订阅者线程池被生产者线程池覆盖掉了,所以显示还是256.
10. 消费者只消费部分产品,并且发生异常
运行结果
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 256 number
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -5
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 2
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -10
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 3
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Exception java.lang.ArithmeticException: / by zero
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: -1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscriber Complete
解析:
打印Subscriber Complete
, 因为异常导致订阅者提前结束。
11. 源码解析
还记得说Publisher在subscribe()调用之前啥都没有处理么?
上面的结果显示,线程先打印subscirbe()
订阅者的线程[ single-1]
,后面才是生产者的线程[ elastic-2]
[ single-1] com.zgpeace.reactor.ReactorApplication : Request 4 number
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Publish Thread[elastic-2,5,main], 1
[ elastic-2] com.zgpeace.reactor.ReactorApplication : Subscribe Thread[elastic-2,5,main]: 1
.map
的实现
public final <V> Flux<V> map(Function<? super T, ? extends V> mapper) {
if (this instanceof Fuseable) {
return onAssembly(new FluxMapFuseable<>(this, mapper));
}
return onAssembly(new FluxMap<>(this, mapper));
}
protected static <T> Flux<T> onAssembly(Flux<T> source) {
Function<Publisher, Publisher> hook = Hooks.onEachOperatorHook;
if(hook != null) {
source = (Flux<T>) hook.apply(source);
}
if (Hooks.GLOBAL_TRACE) {
AssemblySnapshot stacktrace = new AssemblySnapshot(null, Traces.callSiteSupplierFactory.get());
source = (Flux<T>) Hooks.addAssemblyInfo(source, stacktrace);
}
return source;
}
解析:
Publisher的map仅仅是做了数据的封装,没有任何行动。
.subscribe()
的调用
public final Disposable subscribe(@Nullable Consumer<? super T> consumer,
@Nullable Consumer<? super Throwable> errorConsumer,
@Nullable Runnable completeConsumer,
@Nullable Consumer<? super Subscription> subscriptionConsumer) {
return (Disposable)this.subscribeWith(new LambdaSubscriber(consumer, errorConsumer, completeConsumer, subscriptionConsumer, (Context)null));
}
LambdaSubscriber(@Nullable Consumer<? super T> consumer, @Nullable Consumer<? super Throwable> errorConsumer, @Nullable Runnable completeConsumer, @Nullable Consumer<? super Subscription> subscriptionConsumer, @Nullable Context initialContext) {
this.consumer = consumer;
this.errorConsumer = errorConsumer;
this.completeConsumer = completeConsumer;
this.subscriptionConsumer = subscriptionConsumer;
this.initialContext = initialContext == null ? Context.empty() : initialContext;
}
public final <E extends Subscriber<? super T>> E subscribeWith(E subscriber) {
this.subscribe(subscriber);
return subscriber;
}
解析: 上面都是数据组装。
consumer : 过程处理消费者
errorConsumer: 错误处理消费者
completeConsumer:完成处理消费者
subscriptionConsumer: 订阅处理消费者
处理逻辑在this.subscribe(subscriber)
public final void subscribe(Subscriber<? super T> actual) {
CorePublisher publisher = Operators.onLastAssembly(this);
CoreSubscriber subscriber = Operators.toCoreSubscriber(actual);
if (publisher instanceof OptimizableOperator) {
OptimizableOperator operator = (OptimizableOperator)publisher;
while(true) {
subscriber = operator.subscribeOrReturn(subscriber);
if (subscriber == null) {
return;
}
OptimizableOperator newSource = operator.nextOptimizableSource();
if (newSource == null) {
publisher = operator.source();
break;
}
operator = newSource;
}
}
publisher.subscribe(subscriber);
}
代码解析:
调用publisher.subscribe(subscriber)
, 真正触发publisher
的执行
继续点击publisher.subscribe(subscriber)
为接口
void subscribe(CoreSubscriber<? super T> subscriber);
继续看一个实现接口的实例FluxRepeat.java
FluxRepeat.java
的实现
void resubscribe() {
if (WIP.getAndIncrement(this) == 0) {
do {
if (isCancelled()) {
return;
}
long c = produced;
if (c != 0L) {
produced = 0L;
produced(c);
}
source.subscribe(this);
} while (WIP.decrementAndGet(this) != 0);
}
}
才会真正调用source.subscribe(this);
所以在没有subscribe()
之前,Publisher的所有逻辑都没有发生。
12. 代码下载
https://github.com/zgpeace/Spring-Boot2.1/tree/master/reactor/ReactorSimple
13. 参考