简介
Reactor响应式编程(Reactive Programming):
Reactor is a fully non-blocking reactive programming foundation for the JVM, with efficient demand management (in the form of managing “backpressure”). It integrates directly with the Java 8 functional APIs, notably CompletableFuture, Stream, and Duration. It offers composable asynchronous sequence APIs — Flux (for [N] elements) and Mono (for [0|1] elements) — and extensively implements the Reactive Streams specification.
翻译一下就是:Reactor是JVM的一个完全无阻塞的响应式编程基础,具有高效的需求管理(以管理“背压”的形式)。它直接与Java 8功能api集成,尤其是CompletableFuture、Stream和Duration。它提供了可组合的异步序列api——Flux(用于[N]元素)和Mono(用于[0|1]元素)——并且广泛地实现了反应流规范。
背景
我们服务端的项目大多采用了Spring WebFlux,reactor是 Spring WebFlux 的首选反应库,WebFlux 需要 Reactor 作为核心依赖项。Reactor 存在一定的学习成本,在开发中我们遇到了些bug,相当一部分是因为我们不够了解 reactor ,踩了很多坑。所以在本文档中我们主要针对的是一些学习过程容易让新人感到迷茫的知识点(map、flatMap、异步、并发),期望能让新人更好上手 Spring WebFlux。
Mono
Mono<T>是特殊的Publisher<T>,它通过onNext信号最多发出一个项目, 然后以一个onComplete信号(成功Mono,有或没有值)终止,或者只发出一个onError信号。某些Operator(尤其是那些将Mono与其他Publisher结合在一起的Operator)可以把Mono切换到Flux。 例如,Mono.concatWith(Publisher)返回Flux,而Mono.then(Mono)返回另一个Mono。
Flux
Flux<T>是标准的Publisher<T>,表示它是可以发送0到N个元素的异步序列,可选的终止操作有onComplete或onError。 与Reactive Streams规范一样,这三种信号转换为对下游的onNext,onComplete和onError方法的调用。 Flux是通用的响应式类型。 请注意,所有事件,甚至是终止事件,都是可选的,意思是:可能没有onNext事件,但只有onComplete事件,这就表示此Flux是一个空的有限序列。删除onComplete则可以得到一个无限的空序列(除了取消测试外,它没有什么用处)。 同样,无限序列不一定为空。例如,Flux.interval(Duration)无限生产Flux<Long>。
map 、flatMap 以及 flatMapSequential 区别:
方法签名
//Map 的方法签名
<V> Flux<V> map(Function<? super T, ? extends V> mapper)
//FlatMap的方法签名
<R> Flux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper)
map:通过对每个元素应用同步函数来转换此Flux发出的元素, 并且是一对一的转换流元素。
flatMap: 将此Flux发出的元素异步转换为 Publisher,然后通过合并将这些内部发布者扁平化为单个Flux 。Flux可能包含N个元素,所以flatMap是一对多的转换。将各个 publisher 合并的过程不会保持 源Flux发布 的顺序,可能出现交错。
flatMapSequential: 将此Flux发出的元素异步转换为 Publisher,然后通过合并将这些内部发布者扁平化为单个Flux 。于 flatMap 不同的是 flatMapSequential 在合并 publisher 时会 按源元素的顺序合并它们。
map是一个同步运算符,它只是一种将一个值转换为另一个值的方法。
flatMap可以是同步的,也可以是异步的,这取决于flatMap中调用的方法是否使用。
示例1:
void demo() {
//1. Flux.interval 按时生产Long的无限流
final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
.map(log()) //2. 调用方法log,订阅后log()会在当前线程执行
.flatMap(logOfFlux()); //3. 通过flatMap调用 log(), 会在当前线程执行
//完成flux的定义,调用subscribe后才会真正开始执行
flux.subscribe();
}
Function<Long, Long> log() {
return aLong -> {
log.info("num is {}", aLong);
return aLong;
};
}
Function<Long, Flux<Long>> logOfFlux() {
return aLong -> {
log.info("num is {}", aLong);
return Flux.just(aLong);
};
}
在我们的示例代码中,该 flatMap 操作是同步的,因为我们使用Flux.just()方法发出元素。下面我们会介绍如何在 flatMap 中实现异步操作。
上边代码中讲到了 Publisher 调用 subscribe 后才会真正开始执行,但是 subscribe 中的代码并不一定会执行。
当 Mono 是空序列时 :
void monoOfEmpty() {
Mono.empty()
.map(m -> func())
.subscribe(message -> {
responseObserver.onNext(message);
responseObserver.onCompleted();
);
}
会有同学喜欢在 subscribe 中处理响应(例如rpc),但这个场景中responseObserver.onCompleted() 不会被执行。
正确的做法应该是:
void monoOfEmpty() {
Mono.empty()
.map(m -> func())
.doOnSuccess(message -> responseObserver.onCompleted())
.subscribe(responseObserver::onNext);
}
异步与多线程
Reactor被视为与并发无关的。也就是说,获得Flux或Mono并不一定意味着它在专用线程中运行。 取而代之的是,大多数Operator会继续在执行前一个Operator的线程中工作。除非指定,否则最顶层的Operator(源)本身运行在进行subscribe()调用的线程上。
先分享一个案例:
在 Growing 查询服务 olap 中由于使用错误的操作符带来性能问题
现象:在查看某些看板时,响应时间会超时(一分钟)。
经过分析发现这些看板中的单图查询时间段包含“今天”,由于需要保证实时数据,olap在处理包含“今天”的查询时不会生成缓存;通过日志发现olap在查询clickhouse时一直都是一个线程在提交。
对应代码: