一些核心的概念
Operators - Publisher / Subscriber
Publisher / Subscriber 类似于生产者和消费者的关系,在这里作为消息的发布者和订阅者,在Java中类似于集合中的迭代器iterator,对于iterator来说,是我们主动的获取集合中的下一个元素,是我们拉数据的过程,对于Publisher / Subscriber来说,是一种推的方式,是Publisher将下一个序列的元素推送给Subscriber
- Nothing Happens Until You subscribe()
在调用subscribe之前,不会发生任何事情(简单的把所有的操作都包涵在subscribe里面,所以只有当subscribe调用之后,其他的代码才能执行) - Flux [ 0…N ] - onNext()、onComplete()、onError()
N个元素的序列,onNext()方法:遇到下一个元素该执行什么 ;onComplete():当整个序列完成时,执行什么;onError() :遇到错误时,该执行什么。这些方法中的每一个都可以传入lambda表达式去处理。 - Mono [ 0…1 ] - onNext()、onComplete()、onError()
0或者1个元素的序列,onNext()方法:遇到下一个元素该执行什么 ;onComplete():当整个序列完成时,执行什么;onError() :遇到错误时,该执行什么。这些方法中的每一个都可以传入lambda表达式去处理。
Backpressure
指的一种反压力,就是说,在上游Publisher生产速度非常快,而下游Subscriber跟不上上游的速度的时候,我可以做到,既然上游生产了200多个元素,但是订阅者一次只拿几个
- Subscription
- onRequest()、onCancel()、onDispose()
onRequest():每次请求多少个元素
onDispose() :终止订阅的过程
线程调度 Schedulers
- immediate() / single() / newSingle()
immediate():我现在在哪一个线程上,我在当前线程上做后续的动作
single():使用一个可复用的线程
newSingle():新启一个线程去做 - elastic() / parallel() / newParallel()
线程池上的操作
elastic(): 在一个缓存的线程池上面做一个后续的操作,可以做Publisher ,也可以做Subscriber。在elastic线程池在线程空闲60s之后,就会被回收。
parallel() :固定线程池,会创建出和cpu核数相同的线程,这里的线程不会被回收。
错误处理
- onError / onErrorReturn / onErrorResume
onError:相当于trycatch
onErrorReturn :在遇到异常时,返回一个默认值
onErrorResume :用一段特定的lambda处理来做一个异常处理 - doOnError / doFinally
doFinally :不管程序是正常执行完还是异常执行完,都需要执行doFinally的代码。
不指定任何的线程信息,在主线程main上面发生
package geektime.spring.reactor.simple;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
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.Scheduler;
import reactor.core.scheduler.Schedulers;
import javax.security.auth.Subject;
import java.io.Serializable;
/**
* @Author: 13679
* @CreateTime: 2019-12-01 13:15
*/
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ReactorTest.class,args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
Flux.range(1,6) //注意顺序造成的区别
.doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
.doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
// .publishOn(Schedulers.elastic())
.map(i -> { //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的 Thread.currentThread()会打印出当前线程的信息和一个数字
log.info("Publish {},{}",Thread.currentThread(),i);
//return 10/(i-3);
return i;
})
.doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
//.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);
}
}
结果如下:
我们可以看到,一开始请求了一个long的最大值,因为不管生产多少,都需要被消费,使用都会request过来,然后就在main上面不停的Publish Subscribe,每次生产一个,订阅一个,执行完毕之后,打印出COMPLETE 1 和COMPLETE 2。
elastic做Publish和Schedulers.single()让一个单独的线程进行订阅
package geektime.spring.reactor.simple;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
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.Scheduler;
import reactor.core.scheduler.Schedulers;
import javax.security.auth.Subject;
import java.io.Serializable;
/**
* @Author: 13679
* @CreateTime: 2019-12-01 13:15
*/
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ReactorTest.class,args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
Flux.range(1,6) //注意顺序造成的区别
.doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
.doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
.publishOn(Schedulers.elastic()) //publishOn这一行后面的代码,都会执行在elastic线程池里面 elastic做Publish
.map(i -> { //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的 Thread.currentThread()会打印出当前线程的信息和一个数字
log.info("Publish {},{}",Thread.currentThread(),i);
//return 10/(i-3);
return i;
})
.doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
.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);
}
}
结果如下:
现在,request在single上,同时,long的最大值现在变成了256,先取256个元素,然后放到elastic线程池里面来,在elastic池的一个线程里面,进行订阅。
有关错误异常的处理
public void run(ApplicationArguments args) throws Exception {
Flux.range(1,6) //注意顺序造成的区别
.doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
.doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
.publishOn(Schedulers.elastic()) //publishOn这一行后面的代码,都会执行在elastic线程池里面 elastic做Publish
.map(i -> { //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的 Thread.currentThread()会打印出当前线程的信息和一个数字
log.info("Publish {},{}",Thread.currentThread(),i);
return 10/(i-3);
//return i;
})
.doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
.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),在遇到异常时,直接打印-1,同时,Subscriber COMPLETE,说明碰到异常,终止Subscribe操作。
指定接收
package geektime.spring.reactor.simple;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Subscriber;
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.Scheduler;
import reactor.core.scheduler.Schedulers;
import javax.security.auth.Subject;
import java.io.Serializable;
/**
* @Author: 13679
* @CreateTime: 2019-12-01 13:15
*/
@Slf4j
@SpringBootApplication
public class ReactorTest implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ReactorTest.class,args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
Flux.range(1,6) //注意顺序造成的区别
.publishOn(Schedulers.elastic()) //publishOn这一行后面的代码,都会执行在elastic线程池里面 elastic做Publish
.doOnRequest(n->log.info("Request {} number",n)) //打印出请求多少个数的日志
.doOnComplete(()->log.info("Publisher COMPLETE 1"))//1~6publish完了之后,打印个1
.map(i -> { //map实现了元素的转换,这里主要演示map的执行在哪个线程上发生的 Thread.currentThread()会打印出当前线程的信息和一个数字
log.info("Publish {},{}",Thread.currentThread(),i);
//return 10/(i-3);
return i;
})
.doOnComplete(()->log.info("Publisher COMPLETE 2")) //打印完之后 做一个Complete
.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) //只允许接收4个
);
Thread.sleep(2000);
}
}
结果如下:
因为还有2个没有Publish和Subscribe出来,所以不会打印COMPLETE。当然,如果遇到报错,Subscriber COMPLETE会打印出来,因为终止了Subscribe操作。