对于响应式编程的解释
-
个人理解:
Java中传统的线程接口有两种,一种是Runable,一种是Callable;一个没有返回值,一个有返回值;Callable的返回值用Future来接收。无论是Runable还是Callable,都要等线程执行完才能继续操作下去,这个过程中就造成了阻塞。
如果想我把任务分配下去,并且告诉他任务执行完成就怎么怎么样,然后我就不用管了,这样一种思想可以理解成是响应式编程,就像是“好莱坞原则”(演员不用主动联系导演,导演会去联系演员,也就是你不要来找我,我会去找你)。 -
响应式编程就是当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。
-
举个例子:你准备做菜,都准备好了,但是发现没有盐,需要去买,这个买的过程就比较耗时,传统的多线程模式是这样的,你让孩子去超市买,孩子买回来你做饭,但是买的这个过程你是等待着的;而响应式编程的思想是你让孩子去买盐,并且写了一个菜谱给他,他买盐回来就可以按照菜谱做了,做菜这个事情就跟你没有关系了,这样你可以去做其他事情了。
-
权威定义:
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
Spring的响应式编程:reactor-core
reactor设计模式是基于事件驱动的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js等,包括java的nio。
- 首先说一下响应流的特点
- 响应流必须是无阻塞的。
- 响应流必须是一个数据流。
- 它必须可以异步执行。
- 并且它也应该能够处理背压。
背压是反应流中的一个重要概念,可以理解为,生产者可以感受到消费者反馈的消费压力,并根据压力进行动态调整生产速率。
Publisher
-
由于响应流的特点,我们不能再返回一个简单的POJO对象来表示结果了。必须返回一个类似Java中的Future的概念,在有结果可用时通知消费者进行消费响应。
-
Reactive Stream规范中这种被定义为Publisher ,Publisher是一个可以提供0-N个序列元素的提供者,并根据其订阅者Subscriber<? super T>的需求推送元素。一个Publisher可以支持多个订阅者,并可以根据订阅者的逻辑进行推送序列元素。
-
Flux和Mono都是Publisher在Reactor 3实现。Publisher提供了subscribe方法,允许消费者在有结果可用时进行消费。如果没有消费者Publisher不会做任何事情,他根据消费情况进行响应。 Publisher可能返回零或者多个,甚至可能是无限的,为了更加清晰表示期待的结果就引入了两个实现模型Mono和Flux。
Mono
- Mono 是一个发出(emit)0-1个元素的Publisher,可以被onComplete信号或者onError信号所终止。
- Mono创建方法:
- empty():创建一个不包含任何元素,只发布结束消息的序列。
- just():可以指定序列中包含的全部元素。创建出来的 Mono序列在发布这些元素之后会自动结束。
- justOrEmpty():从一个 Optional 对象或可能为 null 的对象中创建 Mono。只有 Optional 对象中包含值或对象不为 null 时,Mono 序列才产生对应的元素。
- error(Throwable error):创建一个只包含错误消息的序列。
- never():创建一个不包含任何消息通知的序列。
- fromCallable()、fromCompletionStage()、fromFuture()、fromRunnable()和 fromSupplier():分别从 Callable、CompletionStage、CompletableFuture、Runnable 和 Supplier 中创建 Mono。
- delay(Duration duration)和 delayMillis(long duration):创建一个 Mono 序列,在指定的延迟时间之后,产生数字 0 作为唯一值。
- 通过 create()方法来使用 MonoSink 来创建 Mono。
Flux
- Flux 是一个发出(emit)0-N个元素组成的异步序列的Publisher,可以被onComplete信号或者onError信号所终止。在响应流规范中存在三种给下游消费者调用的方法 onNext, onComplete, 和onError。
- 创建Flux,通常来讲有4种方法。
- 使用generate:最简单的同步创建的generate。
- 使用create:Flux也提供了一个create方法来创建Flux,create可以是同步也可以是异步的,并且支持多线程操作。
- 使用push:push和create一样,也支持异步操作,但是同时只能有一个线程来调用next, complete 或者 error方法,所以它是单线程的。
- 使用Handle:Handle和上面的三个方法不同,它是一个实例方法。它和generate很类似,也是消费SynchronousSink对象;不同的是它的参数是一个BiConsumer,是没有返回值的。
创建 Mono 和 Flux(开始阶段)
使用 Reactor 编程的开始必然是先创建出 Mono 或 Flux。有些时候不需要我们自己创建,而是实现例如 WebFlux 中的 WebClient 或 Spring Data Reactive 得到一个 Mono 或 Flux。
-
使用 WebFlux WebClient 调用 HTTP 接口
WebClient webClient = WebClient.create("http://localhost:8080"); public Mono<User> findById(Long userId) { return webClient .get() .uri("/users/" + userId) .accept(MediaType.APPLICATION_JSON) .exchange() .flatMap(cr -> cr.bodyToMono(User.class)); } -
使用 ReactiveMongoRepository 查询 User
public interface UserRepository extends ReactiveMongoRepository<User, Long> { Mono<User> findByUsername(String username); }
但有些时候,我们也需要主动地创建一个 Mono 或 Flux。
- 普通的创建方式
Mono<String> helloWorld = Mono.just("Hello World"); Flux<String> fewWords = Flux.just("Hello", "World"); Flux<String> manyWords = Flux.fromIterable(words);
这样的创建方式在什么时候用呢?一般是用在经过一系列 非IO型 操作之后,得到了一个对象。接下来要基于这个对象运用 Reactor 进行 高性能 的 IO 操作时,可以用这种方式将之前得到的对象转换为 Mono 或 Flux。
- 文艺的创建方式
上面是通过一个 同步调用 得到的结果创建出 Mono 或 Flux,但有时需要从一个 非 Reactive 的 异步调用 的结果创建出 Mono 或 Flux。
如果这个 异步方法 返回一个 CompletableFuture,那可以基于这个 CompletableFuture 创建一个 Mono:
Mono.fromFuture(completableFuture);
如果这个 异步调用 不会返回 CompletableFuture,是有自己的 回调方法,那怎么创建 Mono 呢?可以使用 static Mono create(Consumer<MonoSink> callback) 方法:
Mono.create(sink -> {
ListenableFuture<ResponseEntity<String>> entity = asyncRestTemplate.getForEntity(url, String.class);
entity.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
@Override
public void onSuccess(ResponseEntity<String> result) {
sink.success(result.getBody());
}
@Override
public void onFailure(Throwable ex) {
sink.error(ex);
}
});
});
在使用 WebFlux 之后,AsyncRestTemplate 已经不推荐使用,这里只是做演示。
处理 Mono 和 Flux(中间阶段)
中间阶段的 Mono 和 Flux 的方法主要有 filter、map、flatMap、then、zip、reduce 等。这些方法使用方法和 Stream 中的方法类似。
下面举几个 Reactor 开发实际项目的问题,帮大家理解这些方法的使用场景:
- 问题一: map、flatMap 和 then 在什么时候使用
本段内容将涉及到如下类和方法:
方法:Mono.map()
方法:Mono.flatMap()
方法:Mono.then()
类:Function
在 Mono 和 Flux 中间环节的处理过程中,有三个有些类似的方法:map()、flatMap() 和 then()。这三个方法的使用频率很高。
传统的命令式编程
Object result1 = doStep1(params);
Object result2 = doStep2(result1);
Object result3 = doStep3(result2);
对应的反应式编程
Mono.just(params)
.flatMap(v -> doStep1(v))
.flatMap(v -> doStep2(v))
.flatMap(v -> doStep3(v));
从上面两段代码的对比就可以看出来 flatMap() 方法在其中起到的作用,map() 和 then() 方法也有类似的作用。但这些方法之间的区别是什么呢?我们先来看看这三个方法的签名(以 Mono 为例):
flatMap(Function<? super T, ? extends Mono<? extends R>> transformer)
map(Function<? super T, ? extends R> mapper)
then(Mono other)
-
then()
then() 看上去是下一步的意思,但它只表示执行顺序的下一步,不表示下一步依赖于上一步。then() 方法的参数只是一个 Mono,无从接受上一步的执行结果。而 flatMap() 和 map() 的参数都是一个 Function,入参是上一步的执行结果。 -
flatMap() 和 map()
flatMap() 和 map() 的区别在于,flatMap() 中的入参 Function 的返回值要求是一个 Mono 对象,而 map 的入参 Function 只要求返回一个 普通对象。在业务处理中常需要调用 WebClient 或 ReactiveXxxRepository 中的方法,这些方法的 返回值 都是 Mono(或 Flux)。所以要将这些调用串联为一个整体 链式调用,就必须使用 flatMap(),而不是 map()。 -
问题二:如何实现并发执行
本段内容将涉及到如下类和方法:
方法:Mono.zip()
类:Tuple2
类:BiFunction
并发执行 是常见的一个需求。Reactive Programming 虽然是一种 异步编程 方式,但是 异步 不代表就是 并发并行 的。
在 传统的命令式编程 中,并发执行 是通过 线程池 加 Future 的方式实现的。
Future<Result1> result1Future = threadPoolExecutor.submit(() -> doStep1(params));
Future<Result2> result2Future = threadPoolExecutor.submit(() -> doStep2(params));
// Retrive result
Result1 result1 = result1Future.get();
Result2 result2 = result2Future.get();
// Do merge;
return mergeResult;
上面的代码虽然实现了 异步调用,但 Future.get() 方法是 阻塞 的。在使用 Reactor 开发有 并发 执行场景的 反应式代码 时,不能用上面的方式。
这时应该使用 Mono 和 Flux 中的 zip() 方法,以 Mono 为例,代码如下:
Mono<CustomType1> item1Mono = ...;
Mono<CustomType2> item2Mono = ...;
Mono.zip(items -> {
CustomType1 item1 = CustomType1.class.cast(items[0]);
CustomType2 item2 = CustomType2.class.cast(items[1]);
// Do merge
return mergeResult;
}, item1Mono, item2Mono);
上述代码中,产生 item1Mono 和 item2Mono 的过程是 并行 的。比如,调用一个 HTTP 接口的同时,执行一个 数据库查询 操作。这样就可以加快程序的执行。
但上述代码存在一个问题,就是 zip() 方法需要做 强制类型转换。而强制类型转换是 不安全的。好在 zip() 方法存在 多种重载 形式。除了最基本的形式以外,还有多种 类型安全 的形式:
static <T1, T2> Mono<Tuple2<T1, T2>> zip(Mono<? extends T1> p1, Mono<? extends T2> p2);
static <T1, T2, O> Mono<O> zip(Mono<? extends T1> p1, Mono<? extends T2> p2, BiFunction<? super T1, ? super T2, ? extends O> combinator);
static <T1, T2, T3> Mono<Tuple3<T1, T2, T3>> zip(Mono<? extends T1> p1, Mono<? extends T2> p2, Mono<? extends T3> p3);
对于不超过 7 个元素的合并操作,都有 类型安全 的 zip() 方法可选。以两个元素的合并为例,介绍一下使用方法:
Mono.zip(item1Mono, item2Mono).map(tuple -> {
CustomType1 item1 = tuple.getT1();
CustomType2 item2 = tuple.getT2();
// Do merge
return mergeResult;
});
上述代码中,map() 方法的参数是一个 Tuple2,表示一个 二元数组,相应的还有 Tuple3、Tuple4 等。
对于两个元素的并发执行,也可以通过 zip(Mono<? extends T1> p1, Mono<? extends T2> p2, BiFunction<? super T1, ? super T2, ? extends O> combinator) 方法直接将结果合并。方法是传递 BiFunction 实现 合并算法。
- 问题三:集合循环之后的汇聚
本段内容将涉及到如下类和方法:
方法:Flux.fromIterable()
方法:Flux.reduce()
类:BiFunction
另外一个稍微复杂的场景,对一个对象中的一个类型为集合类的(List 、Set)进行处理之后,再对原本的对象进行处理。使用 迭代器模式 的代码很容易编写:
List<SubData> subDataList = data.getSubDataList();
for (SubData item : subDataList) {
// Do something on data and item
}
// Do something on data
当我们要用 Reactive 风格的代码实现上述逻辑时,就不是那么简单了。这里会用到 Flux 的 reduce() 方法。reduce() 方法的签名如下:
<A> Mono<A> reduce(A initial, BiFunction<A, ? super T, A> accumulator);
可以看出,reduce() 方法的功能是将一个 Flux 聚合 成一个 Mono。
第一个参数: 返回值 Mono 中元素的 初始值。
第二个参数: 是一个 BiFunction,用来实现 聚合操作 的逻辑。对于泛型参数 <A, ? super T, A> 中:
第一个 A: 表示每次 聚合操作 之后的 结果的类型,它作为 BiFunction.apply() 方法的 第一个入参;
第二个 ? super T: 表示集合中的每个元素的类型,它作为 BiFunction.apply() 方法的 第二个入参;
第三个 A: 表示聚合操作的 结果,它作为 BiFunction.apply() 方法的 返回值。
接下来看一下示例:
Data initData = ...;
List<SubData> list = ...;
Flux.fromIterable(list)
.reduce(initData, (data, itemInList) -> {
// Do something on data and itemInList
return data;
});
上面的示例代码中,initData 和 data 的类型相同。执行完上述代码之后,reduce() 方法会返回 Mono。
消费 Mono 和 Flux(结束阶段)
直接消费的 Mono 或 Flux 的方式就是调用 subscribe() 方法。如果在 WebFlux 接口中开发,直接返回 Mono 或 Flux 即可。WebFlux 框架会完成最后的 Response 输出工作。
记参考文章
- 参考文章1:https://www.jianshu.com/p/eef7ebe28673
- 参考文章2:https://zhuanlan.zhihu.com/p/251238873
- 参考文章3:https://projectreactor.io/docs/core/release/reference/
- 参考文章4:https://www.cnblogs.com/flydean/p/13946939.html
- 参考文章5:https://blog.csdn.net/songhaifengshuaige/article/details/79248343
6148

被折叠的 条评论
为什么被折叠?



