个人理解:
Java中传统的线程接口有两种,一种是Runable,一种是Callable;一个没有返回值,一个有返回值;Callable的返回值用Future来接收。无论是Runable还是Callable,都要等线程执行完才能继续操作下去,这个过程中就造成了阻塞。
如果想我把任务分配下去,并且告诉他任务执行完成就怎么怎么样,然后我就不用管了,这样一种思想可以理解成是响应式编程,就像是“好莱坞原则”(演员不用主动联系导演,导演会去联系演员,也就是你不要来找我,我会去找你)。
响应式编程就是当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。
举个例子:你准备做菜,都准备好了,但是发现没有盐,需要去买,这个买的过程就比较耗时,传统的多线程模式是这样的,你让孩子去超市买,孩子买回来你做饭,但是买的这个过程你是等待着的;而响应式编程的思想是你让孩子去买盐,并且写了一个菜谱给他,他买盐回来就可以按照菜谱做了,做菜这个事情就跟你没有关系了,这样你可以去做其他事情了。
权威定义:
响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
reactor设计模式是基于事件驱动的一种实现方式,处理多个客户端并发的向服务端请求服务的场景。每种服务在服务端可能由多个方法组成。reactor会解耦并发请求的服务并分发给对应的事件处理器来处理。目前,许多流行的开源框架都用到了reactor模式,如:netty、node.js等,包括java的nio。
由于响应流的特点,我们不能再返回一个简单的POJO对象来表示结果了。必须返回一个类似Java中的Future的概念,在有结果可用时通知消费者进行消费响应。
Reactive Stream规范中这种被定义为Publisher ,Publisher是一个可以提供0-N个序列元素的提供者,并根据其订阅者Subscriber super T>的需求推送元素。一个Publisher可以支持多个订阅者,并可以根据订阅者的逻辑进行推送序列元素。
Flux和Mono都是Publisher在Reactor 3实现。Publisher提供了subscribe方法,允许消费者在有结果可用时进行消费。如果没有消费者Publisher不会做任何事情,他根据消费情况进行响应。 Publisher可能返回零或者多个,甚至可能是无限的,为了更加清晰表示期待的结果就引入了两个实现模型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。
如果这个 异步方法 返回一个 CompletableFuture,那可以基于这个 CompletableFuture 创建一个 Mono:
Mono.fromFuture(completableFuture);
如果这个 异步调用 不会返回 CompletableFuture,是有自己的 回调方法,那怎么创建 Mono 呢?可以使用 static Mono create(Consumer
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 的方法主要有 filter、map、flatMap、then、zip、reduce 等。这些方法使用方法和 Stream 中的方法类似。
下面举几个 Reactor 开发实际项目的问题,帮大家理解这些方法的使用场景:
方法: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 result1Future = threadPoolExecutor.submit(() -> doStep1(params));
Future 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 item1Mono = ...;
Mono 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 Mono> zip(Mono extends T1> p1, Mono extends T2> p2);
static Mono zip(Mono extends T1> p1, Mono extends T2> p2, BiFunction super T1, ? super T2, ? extends O> combinator);
static Mono> 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 subDataList = data.getSubDataList();
for (SubData item : subDataList) {
// Do something on data and item
}
// Do something on data
当我们要用 Reactive 风格的代码实现上述逻辑时,就不是那么简单了。这里会用到 Flux 的 reduce() 方法。reduce() 方法的签名如下:
Mono reduce(A initial, BiFunction accumulator);
可以看出,reduce() 方法的功能是将一个 Flux 聚合 成一个 Mono。
第一个参数: 返回值 Mono 中元素的 初始值。
第二个参数: 是一个 BiFunction,用来实现 聚合操作 的逻辑。对于泛型参数 中:
第一个 A: 表示每次 聚合操作 之后的 结果的类型,它作为 BiFunction.apply() 方法的 第一个入参;
第二个 ? super T: 表示集合中的每个元素的类型,它作为 BiFunction.apply() 方法的 第二个入参;
第三个 A: 表示聚合操作的 结果,它作为 BiFunction.apply() 方法的 返回值。
接下来看一下示例:
Data initData = ...;
List list = ...;
Flux.fromIterable(list)
.reduce(initData, (data, itemInList) -> {
// Do something on data and itemInList
return data;
});
上面的示例代码中,initData 和 data 的类型相同。执行完上述代码之后,reduce() 方法会返回 Mono。
直接消费的 Mono 或 Flux 的方式就是调用 subscribe() 方法。如果在 WebFlux 接口中开发,直接返回 Mono 或 Flux 即可。WebFlux 框架会完成最后的 Response 输出工作。