1. 概述
响应式编程基于观察者模型,以异步非阻塞方式处理问题。在2009 年,由微软提出的一个实现异步编程的方式,随后java语言也很快跟进,例如RxJava、Akka Stream 等技术。
reactor是一个反应式编程库,它是Spring 5反应式编程的基础。该类库遵循Reactive Streams规范。
2. 类库
2.1 reactive-streams
反应式规范,主要有4个接口 Publisher,Subscriber,Subscription,Processor。
Publisher负责生成数据,并将数据发送给Subscriber。
Subscriber订阅Publisher,订阅成功后,会得到一个Subscription对象,Subscriber可以通过Subscription提供的request、cancle方法来管理其订阅。
Processor是一个处理器,负责处理数据,从接口上看,它是一个发布者和订阅者的组合。
2.2 reactor
Reactor有两种核心类型:一个是Mono, 另一个是Flux,二者都继承了Publisher接口,Mono 负责0到1个元素的处理,Flux负责0到N个元素的处理。
3. 操作
Reactor 中所提供的操作符非常丰富,本文只介绍常见的操作,以下按创建、组合、转换、过滤、事件的大致分类来介绍。
3.1 创建
通过简单数据类型,创建数据。
Mono.just(1)
.subscribe(System.out::println);
Flux.just(1, 2, 3)
.subscribe(System.out::println);
Flux.range(1, 5)
.subscribe(System.out::println);
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
Flux.fromIterable(list1)
.subscribe(System.out::println);
create方法
Mono.create(x -> {
x.success("aa");
})
.subscribe(System.out::println);
Flux.create(sink -> {
for (int i = 0; i < 5; i++) {
sink.next("aa" + i);
}
sink.complete();
})
.subscribe(System.out::println);
generate方法
Flux.generate(
// 生成一个对象,用作状态
AtomicInteger::new,
(state, sink) -> {
int i = state.getAndIncrement();
sink.next("aa" + i);
if (i == 5) {
sink.complete();
}
return state;
},
// 结束时,输出状态
(state) -> System.out.println("state:" + state)
).subscribe(System.out::println);
3.2 组合
merge、mergeSequential方法
Flux.merge(
Flux.interval(Duration.ofSeconds(1)).take(3).map(x -> "a-" + x),
Flux.interval(Duration.ofSeconds(2)).take(3).map(x -> "b-" + x))
.subscribe(System.out::println);
输出结果:
a-0
a-1
b-0
a-2
b-1
b-2
Flux.mergeSequential(
Flux.interval(Duration.ofSeconds(1)).take(3).map(x -> "a-" + x),
Flux.interval(Duration.ofSeconds(2)).take(3).map(x -> "b-" + x))
.subscribe(System.out::println);
输出:
a-0
a-1
a-2
b-0
b-1
b-2
通过输出可发现,merge是按流的自然序列合并输出,而mergeSequential则以流为单位,一个流一个流输出。
concatMap、concatWith方法
Flux.just(10, 20)
.concatMap( x -> Flux.range(x, 2))
.subscribe(System.out::println);
输出:
10
11
20
21
Flux.just(10, 20)
.concatWith(Flux.just(30, 40))
.subscribe(System.out::println);
输出:
10
20
30
40
zipWith方法
Flux.just("a", "b")
.zipWith(Flux.just("c", "d"), (s1, s2) -> String.format("%s-%s", s1, s2))
.subscribe(System.out::println);
输出:
a-c
b-d
3.3 转换
map可以将原数据,映射为另一种数据类型,map对数据的变换是1对1
Mono.just(1)
.map(x -> new Apple(x, "aa"))
.subscribe(System.out::println);
Flux.range(10, 2)
.map(x -> new Apple(x, "aa"))
.subscribe(System.out::println);
flatMap可对每个元素进行一对多的转换,跟concatMap很相似,区别是flatMap不保证转换后的元素的顺序,而concatMap保证转换后的顺序,与原数据顺序一致。
Flux.just(10, 20)
.flatMap( x -> Flux.range(x, 2))
.subscribe(System.out::println);
3.4 过滤
filter对流中数据过滤,只允许满足条件的数据通过。
Flux.range(1, 10)
.filter(i -> i % 2 == 0)
.subscribe(System.out::println);
3.5 事件
除了丰富的操作符,reactor也提供了对数据处理各阶段的事件监听方法,样例如下:
Flux.range(10, 3)
.doOnRequest(x -> {
System.out.println(String.format("请求%d个数据", x));
})
.doOnSubscribe(x -> {
System.out.println("订阅开始");
})
.doOnNext(x -> {
System.out.println("订阅数据:" + x);
})
.doOnComplete(() -> {
System.out.println("订阅完成");
})
.doOnError(x -> {
System.out.println("订阅异常:" + x.getMessage());
})
.then()
.subscribe(System.out::println);
输出:
订阅开始
请求9223372036854775807个数据
订阅数据:10
订阅数据:11
订阅数据:12
订阅完成
通过上面的输出结果,可清晰的了解数据处理的流程。
3.6 其他
除了上面提到的,下面整理了其他几个有趣的操作符。
buffer,它将当前流中的元素收集到集合当中,并将集合对象作为流的新元素。
Flux.range(1, 10)
.buffer(3)
.subscribe(System.out::println);
输出:
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
windows,它将当前流中的元素收集到固定大小的流当中,并将收集的流对象作为流的新元素。
Flux.range(1, 10)
.window(3)
.subscribe(x -> {
System.out.println("------------");
x.subscribe(System.out::println);
});
输出:
------------
1
2
3
------------
4
5
6
------------
7
8
9
------------
10
4. 线程池
响应式流处理过程中,一些业务逻辑,需要进行线程隔离处理,可通过publishOn,subscribeOn两个方法配置Scheduler任务调度器,样例如下:
Mono.just(1)
.map(x -> {
System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
return x + 100;
})
.publishOn(Schedulers.newSingle("single-publishOn"))
.filter(x -> {
System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
return true;
})
.subscribeOn(Schedulers.newSingle("single-subscribeOn"))
.doOnNext(x -> {
System.out.println(String.format("thread: %s, x: %d", Thread.currentThread().getName(), x));
})
.subscribe(System.out::println);
输出:
thread: single-subscribeOn-1, x: 1
thread: single-publishOn-2, x: 101
thread: single-publishOn-2, x: 101
101
(1)publishOn、subscribeOn二者区别
虽然publishOn 和 subscribeOn 两个方法都可以配置Scheduler,但二者的作用范围不同,
publishOn 仅对之后的operator 起作用,而subscribeOn 则从源头影响整个执行过程。
(2)Schedulers
通过Schedulers工具类,可创建多种类型的线程池。
// 单个线程
Schedulers.newSingle("xxx");
// 线程数为内核数
Schedulers.newParallel("xxx");
// 可对线程数、任务队列容量, 按需配置
Schedulers.newBoundedElastic(5, 10, "xxx");
(3)自定义
Executor executor = new ThreadPoolExecutor(
2, //corePoolSize
10, //maximumPoolSize
30L, TimeUnit.SECONDS, //keepAliveTime, unit
new LinkedBlockingQueue<>(1000), //workQueue
Executors.defaultThreadFactory()
);
Schedulers.fromExecutor(executor);
5 策略
Publisher生产数据的速度大于Subscriber消费能力时,就会出现溢出问题,Reactor通过OverflowStrategy枚举参数,提供了多种处理策略:
IGNORE:完全忽略下游背压请求。
ERROR:当下游无法跟上时,发出IllegalStateException信号。
DROP:如果下游没有准备好接收传入信号,则丢弃传入。
LATEST:下游将仅获得来自上游的最新信号。
BUFFER:如果下游跟不上,缓冲所有信号。(默认)
例子:
public class Demo1{
public static void main(String[] args) {
Flux.create(sink -> {
for (int i = 0; i < 100; i++) {
TestUtils.sleep(100);
sink.next(i);
}
sink.complete();
}, OverflowStrategy.LATEST)
.publishOn(Schedulers.newSingle("test-publish"), 5)
.subscribe(new MySubscriber());
}
public static class MySubscriber extends BaseSubscriber {
@Override
protected void hookOnSubscribe(Subscription subscription) {
System.out.println("订阅开始!");
request(1);
}
@Override
protected void hookOnNext(Object value) {
TestUtils.sleep(1000);
System.out.println("消费数据: " + value);
request(1);
}
}
}
输出:
订阅开始!
消费数据: 0
消费数据: 1
消费数据: 2
消费数据: 3
消费数据: 4
消费数据: 36
消费数据: 37
消费数据: 38
消费数据: 39
消费数据: 71
消费数据: 72
消费数据: 73
消费数据: 74
消费数据: 99