随着业务的发展,微服务应用的流量越来越大,使用到的资源也越来越多。
在微服务架构下,大量的应用都是 SpringCloud 分布式架构,这种架构总体上是全链路同步模式。
全链路同步模式不仅造成了资源的极大浪费,并且在流量发生激增波动的时候,受制于系统资源而无法快速的扩容。
全球后疫情时代,降本增效是大背景。如何降本增效?
可以通过技术升级,全链路同步模式 ,升级为 全链路异步模式。
先回顾一下全链路同步模式架构图
全链路同步模式 ,如何升级为 全链路异步模式, 就是一个一个 环节的异步化。
40岁老架构师尼恩,持续深化自己的3高架构知识宇宙,当然首先要去完成一次牛逼的全链路异步模式 微服务实操,下面是尼恩的实操过程、效果、压测数据(性能足足提升10倍多)。
全链路异步模式改造 具体的内容,请参考尼恩的深度文章:全链路异步,让你的 SpringCloud 性能优化10倍+
并且,上面的文章,作为尼恩 全链路异步的架构知识,收录在《尼恩Java面试宝典》V46版的架构专题中
全链路异步化改造,性能提升十倍是大好事,那么,全链路同步模式改造的问题是什么呢?
全链路异步化改造的技术基础,是响应式编程,关键问题在于: 响应式编程的知识太难。
古语说: 蜀道难难于上青天。
很多小伙伴认为: 响应式编程, 比蜀道还难?
所以,40岁老架构师 使用20年的编程功力,给大家呈上一篇, 学习响应式编程 的超级长文,也是一篇超级、超级详细,超级超级全面,并且不断迭代的文章:
《响应式圣经:10W字实现响应式编程自由》
此文,目前为V2版本,新版本是基于V1老版本是尼恩之前写的一篇深受好评的博客文章
Flux、Mono、Reactor 实战(史上最全)
后续,尼恩会一直不断迭代, 为大家拉涅出一本史上最棒的 《响应式圣经》 ,帮助大家实现响应式编程自由。
从此: 蜀道,不再难。
注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从这里获取:码云
为了应对高并发服务器端开发场景,在2009 年,微软提出了一个更优雅地实现异步编程的方式——Reactive Programming,我们称之为响应式编程。
随后,Netflix 和LightBend 公司提供了RxJava 和Akka Stream 等技术,使得Java 平台也有了能够实现响应式编程的框架。
在2017 年9 月28 日,Spring 5 正式发布。
Spring 5 发布最大的意义在于,它将响应式编程技术的普及向前推进了一大步。
而同时,作为在背后支持Spring 5 响应式编程的框架Spring Reactor,也进入了里程碑式的3.1.0 版本。
响应式编程是一种面向数据流和变化传播的编程范式。
这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
响应式编程基于reactor(Reactor 是一个运行在 Java8 之上的响应式框架)的思想,当你做一个带有一定延迟的才能够返回的io操作时,不会阻塞,而是立刻返回一个流,并且订阅这个流,当这个流上产生了返回数据,可以立刻得到通知并调用回调函数处理数据。
电子表格程序就是响应式编程的一个例子。有些单元个含有公式,比如可以包含字面值或类似"=B1+C1"的公式,这些单元格的值,会依据其他单元格的值的变化而变化。
响应式传播核心特点之一:变化传播。
一个单元格变化之后,会像多米诺骨牌一样,导致直接和间接引用它的其他单元格均发生相应变化。
有了 Reactive Streams 这种标准和规范,利用规范可以进行响应式编程。
那再了解下什么是 Reactive programming 响应式编程。
响应式编程是基于异步和事件驱动的非阻塞程序,只是垂直通过在 JVM 内启动少量线程扩展,而不是水平通过集群扩展。
异步调用 +IO Reactor 事件驱动,可以避免将 CPU 浪费在等待网络 IO 和磁盘 IO 时上,实现提高资源使用率。
Reactive programming就是一个编程范例,具体项目中如何体现呢?
响应式项目编程实战中,通过基于 Reactive Streams 规范实现的框架Spring Reactor 去实战。
Spring Reactor 一般提供两种响应式 API :
Mono
:实现发布者,并返回 0 或 1 个元素Flux
:实现发布者,并返回 N 个元素上面讲了响应式编程是什么:
响应式编程(reactive programming)是一种基于数据流(data stream)和变化传递(propagation of change)的声明式(declarative)的编程范式
也讲解了数据流/变化传递/声明式是什么意思,但说到响应式编程就离不开异步非阻塞。
从Spring官网介绍WebFlux的信息我们就可以发现asynchronous, nonblocking
这样的字样,因为响应式编程它是异步的,也可以理解成变化传递它是异步执行的。
Observable类:此类表示可观察对象,或模型视图范例中的“数据”。
它可以被子类实现以表示应用程序想要观察的对象。
//想要观察的对象 ObserverDemo
public class ObserverDemo extends Observable {
public static void main(String[] args) {
ObserverDemo observerDemo = new ObserverDemo();
//添加观察者
observerDemo.addObserver((o,arg)->{
System.out.println("数据发生变化A");
});
observerDemo.addObserver((o,arg)->{
System.out.println("数据发生变化B");
});
observerDemo.setChanged();//将此Observable对象标记为已更改
observerDemo.notifyObservers();//如果该对象发生了变化,则通知其所有观察者
}
}
启动程序测试:
rxjava中,可以使用Observable.create() 该方法接收一个Obsubscribe对象
Observable<Integer> observable = Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
}
});
来个大点的例子:
Observable<Integer> observable=Observable.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
for(int i=0;i<5;i++){
subscriber.onNext(i);
}
subscriber.onCompleted();
}
});
//Observable.subscribe(Observer),Observer订阅了Observable
Subscription subscribe = observable.subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
Log.e(TAG, "完成");
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "异常");
}
@Override
public void onNext(Integer integer) {
Log.e(TAG, "接收Obsverable中发射的值:" + integer);
}
});
输出:
接收Obsverable中发射的值:0
接收Obsverable中发射的值:1
接收Obsverable中发射的值:2
接收Obsverable中发射的值:3
接收Obsverable中发射的值:4
从上面的例子可以看出,在Observer订阅了Observable后,
Observer作为OnSubscribe中call方法的参数传入,从而调用了Observer的相关方法
Reactor 是一个运行在 Java8 之上满足 Reactice 规范的响应式框架,它提供了一组响应式风格的 API。
Reactor 有两个核心类: Flux
和 Mono
,这两个类都实现 Publisher 接口。
Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号:元素值,错误信号,完成信号;错误信号和完成信号都代表终止信号,终止信号用于告诉订阅者数据流结束了,错误信号终止数据流同时把错误信息传递给订阅者。
三种信号的特点:
<dependency>
<groupId>org.projectreactorgroupId>
<artifactId>reactor-coreartifactId>
dependency>
just():创建Flux序列,并声明指定数据流
subscribe():订阅Flux序列,只有进行订阅后才回触发数据流,不订阅就什么都不会发生
public class TestReactor {
public static void main(String[] args) {
//just():创建Flux序列,并声明数据流,
Flux<Integer> integerFlux = Flux.just(1, 2, 3, 4);//整形
//subscribe():订阅Flux序列,只有进行订阅后才回触发数据流,不订阅就什么都不会发生
integerFlux.subscribe(System.out::println);
Flux<String> stringFlux = Flux.just("hello", "world");//字符串
stringFlux.subscribe(System.out::println);
//fromArray(),fromIterable()和fromStream():可以从一个数组、Iterable 对象或Stream 对象中创建Flux序列
Integer[] array = {1,2,3,4};
Flux.fromArray(array).subscribe(System.out::println);
List<Integer> integers = Arrays.asList(array);
Flux.fromIterable(integers).subscribe(System.out::println);
Stream<Integer> stream = integers.stream();
Flux.fromStream(stream).subscribe(System.out::println);
}
}
启动测试:
要搞清楚这两个概念,必须说一下响应流规范。
它是响应式编程的基石。他具有以下特点:
一般由以下组成:
一般由以下组成:
publisher
:发布者,发布元素到订阅者subscriber
:订阅者,消费元素subscription
:订阅,在发布者中,订阅被创建时,将与订阅者共享processor
:处理器,发布者与订阅者之间处理数据,包含了发布者与订阅者的共同体publisher接口规范
public interface Publisher<T> {
void subscribe(Subscriber<? super T> var1); //添加订阅者
}
subscriber接口规范
public interface Subscriber<T> {
void onSubscribe(Subscription var1);
void onNext(T var1);
void onError(Throwable var1);
void onComplete();
}
subscription接口规范
public interface Subscription {
void request(long var1);
void cancel();
}
processor接口规范
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}
Callback
:异步方法采用一个callback作为参数,当结果出来后回调这个callback,例如swings的EventListenerFuture
:异步方法返回一个Future
,此时结果并不是立刻可以拿到,需要处理结束之后才可以使用Future局限
Mono
实现了 org.reactivestreams.Publisher 接口,代表0到1个元素的响应式序列。Flux
同样实现了 org.reactivestreams.Publisher 接口,代表0到N个元素的结果。由于响应流的特点,我们不能再返回一个简单的POJO对象来表示结果了。
必须返回一个类似Java中的Future
的概念,在有结果可用时通知消费者进行消费响应。
Reactive Stream规范中这种被定义为Publisher
Publisher
是一个可以提供0-N个序列元素的提供者,并根据其订阅者Subscriber super T>
的需求推送元素。
一个Publisher
可以支持多个订阅者,并可以根据订阅者的逻辑进行推送序列元素。
下面这个Excel计算就能说明一些Publisher
的特点。
A1-A9就可以看做Publisher
及其提供的元素序列。
A10-A13分别是求和函数SUM(A1:A9)
、平均函数AVERAGE(A1:A9)
、最大值函数MAX(A1:A9)
、最小值函数MIN(A1:A9)
,
A10-A13可以看作订阅者Subscriber
。
假如说我们没有A10-A13,那么A1-A9就没有实际意义,它们并不产生 计算。
这也是响应式的一个重要特点:当没有订阅时发布者什么也不做。
而Flux和Mono都是Publisher
在Reactor 3 实现类。
Publisher
提供了subscribe
方法,允许增加订阅的消费者,订阅之后,消费者在有结果可用时进行消费。
如果没有消费者,Publisher
不会做任何事情,所以,Publisher根据消费情况进行响应。
Publisher
可能返回零或者多个,甚至可能是无限的,为了更加清晰表示期待的结果就引入了两个实现模型Mono和Flux。
异步处理将I/O或计算与调用该操作的线程进行解耦。
异步处理返回结果的未来句柄,通常是java.util.concurrent.Future
或类似的东西,它返回单个对象,集合或异常。
使用Java 8或Promise模式,可以设置future的线性链接,以便发出后续的异步请求。 一旦需要条件处理,就必须中断并同步异步流。 尽管这种方法是可行的,但它并未充分利用异步处理的优势。
Publisher
支持值甚至是无限流的发射序列,而不仅是单个标量值的发射(如Future那样)。 一旦开始处理流而不是单个值,你将非常感谢这个事实。
Project Reactor的词汇表使用两种类型:Mono
和Flux
,它们都是发布者。
Mono
可以发出0到1个事件,而Flux
可以发出0到N个事件。
Publisher
不会偏向某些特定的并发性或异步性来源,也不会偏向于在ThreadPool
中运行基础代码的执行方式-同步还是异步。 作为Publisher
的消费者,你将实际的实现留给了生产者,生产者可以在以后修改它而无需修改代码。
Publisher
的最后一个关键点是,底层处理不是在获取Publisher
时开始的,而是在观察者订阅或向 Publisher
发出信号的那一刻开始的。
这与java.util.concurrent.Future
至关重要,后者在创建/获取(created/obtained)时在某个地方启动。 因此,如果没有观察者订阅Publisher
,则将不会发生任何事情。
event | Iterable (pull) | Publisher (push) |
---|---|---|
retrieve data | T next() | onNext(T) |
discover error | throws Exception | onError(Exception) |
complete | !hasNext() | onCompleted() |
与发布者合作时,你要做的第一件事就是消费它们。
消费发布者意味着订阅它。
这是一个订阅并打印所有发出的项目的示例:
Flux.just("Ben", "Michael", "Mark").subscribe(new Subscriber<String>() {
public void onSubscribe(Subscription s) {
s.request(3);
}
public void onNext(String s) {
System.out.println("Hello " + s + "!");
}
public void onError(Throwable t) {
}
public void onComplete() {
System.out.println("Completed");
}
});
该示例打印以下行:
Hello BenHello MichaelHello MarkCompleted
你可以看到订阅者(或观察者)收到每个事件的通知,并且还接收到已完成的事件。
Publisher
会发出项目(items),直到引发异常或Publisher
完成调用onCompleted
的发出为止。 在那之后不再发出其他元素。
对subscribe
的调用会注册一个允许取消的Subscription
,因此不会接收其他事件。
一旦订阅者从Publisher
中取消订阅,发布者便可以与取消订阅和免费资源进行互操作。
实现Subscriber
需要实现多种方法,因此让我们将代码重写为更简单的形式:
Flux.just("Ben", "Michael", "Mark").doOnNext(new Consumer<String>() {
public void accept(String s) {
System.out.println("Hello " + s + "!");
}
}).doOnComplete(new Runnable() {
public void run() {
System.out.println("Completed");
}
}).subscribe();
或者,使用Java 8 Lambdas甚至更简单:
Flux.just("Ben", "Michael", "Mark")
.doOnNext(s -> System.out.println("Hello " + s + "!"))
.doOnComplete(() -> System.out.println("Completed"))
.subscribe();
你可以使用运算符控制Subscriber
处理的元素。
如果仅对前N
个元素感兴趣,take()
运算符将限制发射项目的数量。
Flux.just("Ben", "Michael", "Mark") //
.doOnNext(s -> System.out.println("Hello " + s + "!"))
.doOnComplete(() -> System.out.println("Completed"))
.take(2)
.subscribe();
该示例打印以下行:
Hello Ben
Hello Michael
Completed
请注意,一旦发出预期的元素计数,take操作符就会从Publisher
隐式取消其订阅。
可以通过另一个Flux或Subscriber来完成对Publisher
的订阅。
除非要实现自定义Publisher,否则请始终使用Subscriber。
上例中使用的订阅者Consumer不处理异常,因此一旦引发异常,你将看到如下堆栈跟踪:
Exception in thread "main" reactor.core.Exceptions$BubblingException: java.lang.RuntimeException: Example exception
at reactor.core.Exceptions.bubble(Exceptions.java:96)
at reactor.core.publisher.Operators.onErrorDropped(Operators.java:296)
at reactor.core.publisher.LambdaSubscriber.onError(LambdaSubscriber.java:117)
...
Caused by: java.lang.RuntimeException: Example exception
at demos.lambda$example3Lambda$4(demos.java:87)
at reactor.core.publisher.FluxPeekFuseable$PeekFuseableSubscriber.onNext(FluxPeekFuseable.java:157)
... 23 more
始终建议从一开始就实施错误处理程序。 在某些时候,事情可能并且会出错。
完全实现的订阅者声明onCompleted
和onError
方法,使你可以对以下事件作出反应:
Flux.just("Ben", "Michael", "Mark").subscribe(new Subscriber<String>() {
public void onSubscribe(Subscription s) {
s.request(3);
}
public void onNext(String s) {
System.out.println("Hello " + s + "!");
}
public void onError(Throwable t) {
System.out.println("onError: " + e);
}
public void onComplete() {
System.out.println("Completed");
}
});
上面的示例说明了如何以一种非阻塞式或非阻塞式执行的方式设置发布者。
Flux
可以显式转换为Iterable
或与block()
同步。
开始表达代码内部执行的本质时,请避免在代码中调用block()
。
调用block()
消除了应用程序反应链(reactive chain)的所有非阻塞优势。
String last = Flux.just("Ben", "Michael", "Mark").last().block();
System.out.println(last);
该示例打印以下行:
Mark
阻塞调用可用于同步发布者链(chain),并找到进入普通且众所周知的Pull
模式的方法。
List<String> list = Flux.just("Ben", "Michael", "Mark").collectList().block();
System.out.println(list);
toList
运算符收集所有发出的元素,并将列表通过BlockingPublisher
传递。
该示例打印以下行:
[Ben, Michael, Mark]
Flux 是一个发出(emit)0-N
个元素组成的异步序列的Publisher
,可以被onComplete
信号或者onError
信号所终止。
在响应流规范中存在三种给下游消费者调用的方法 onNext
, onComplete
, 和onError
。
下面这张图表示了Flux的抽象模型:
以上的的讲解对于初次接触反应式编程的依然是难以理解的,所以这里有一个循序渐进的理解过程。
有些类比并不是很妥当,但是对于你循序渐进的理解这些新概念还是有帮助的。
我们在平常是这么写的:
public List<User> allUsers() {
return Arrays.asList(new User("A"),new User("B"));
}
我们通过迭代返回值List
来get
这些元素进行再处理(消费),不管有没有消费者, 菜品都会生产出来。
在Java 8中我们可以改写为流的表示:
public Stream<User> allUsers() {
return Stream.of(new User("A"),new User("B"));
}
在Reactor中我们又可以改写为Flux表示:
public Flux<User> allUsers(){
return Flux.just(new User("A"),new User("B"));
}
这时候食客来了,发生了订阅,厨师才开始做。
Flux ints = Flux.range(1, 4);
Flux seq1 = Flux.just("bole1", "bole2", "bole3");
List iterable = Arrays.asList("bole_01", "bole_02", "bole_03");
Flux seq2 = Flux.fromIterable(iterable);
seq2.subscribe(i -> System.out.println(i));
Mono
Mono 是一个发出(emit)0-1
个元素的Publisher
,可以被onComplete
信号或者onError
信号所终止。
mono 整体和Flux差不多,只不过这里只会发出0-1个元素。也就是说不是有就是没有。
象Flux一样,我们来看看Mono的演化过程以帮助理解。
public User currentUser () {
return isAuthenticated ? new User("felord.cn", "reactive") : null;
}
直接返回符合条件的对象或者null`。
public Optional<User> currentUser () {
return isAuthenticated ? Optional.of(new User("felord.cn", "reactive"))
: Optional.empty();
}
这个Optional我觉得就有反应式的那种味儿了,当然它并不是反应式。当我们不从返回值Optional取其中具体的对象时,我们不清楚里面到底有没有,但是Optional是一定客观存在的,不会出现NPE问题。
public Mono<User> currentUser () {
return isAuthenticated ? Mono.just(new User("felord.cn", "reactive"))
: Mono.empty();
}
和Optional有点类似的机制,当然Mono不是为了解决NPE问题的,它是为了处理响应流中单个值(也可能是Void
)而存在的。
Mono data = Mono.just("bole");
Mono noData = Mono.empty();
m.subscribe(i -> System.out.println(i));
反应式编程,常常和函数式编程结合,这就是让大家困扰的地方
接口函数名 | 说明 |
---|---|
BiConsumer | 表示接收两个输入参数和不返回结果的操作。 |
BiFunction | 表示接受两个参数,并产生一个结果的函数。 |
BinaryOperator | 表示在相同类型的两个操作数的操作,生产相同类型的操作数的结果。 |
BiPredicate | 代表两个参数谓词(布尔值函数)。 |
BooleanSupplier | 代表布尔值结果的提供者。 |
Consumer | 表示接受一个输入参数和不返回结果的操作。 |
DoubleBinaryOperator | 代表在两个double值操作数的运算,并产生一个double值结果。 |
DoubleConsumer | 表示接受一个double值参数,不返回结果的操作。 |
DoubleFunction | 表示接受double值参数,并产生一个结果的函数。 |
DoublePredicate | 代表一个double值参数谓词(布尔值函数)。 |
DoubleSupplier | 表示表示接受double值参数,并产生一个结果的函数。值结果的提供者。 |
DoubleToIntFunction | 表示接受一个double值参数,不返回结果的操作。 |
DoubleFunction | 表示接受double值参数,并产生一个结果的函数。 |
DoublePredicate | 代表一个double值参数谓词(布尔值函数)。 |
DoubleSupplier | DoubleToIntFunction |
DoubleToIntFunction | 表示接受double值参数,并产生一个int值结果的函数。 |
DoubleToLongFunction | 表示上产生一个double值结果的单个double值操作数的操作。 |
Function | 代表接受一个double值参数,并产生一个long值结果的函数。 |
DoubleUnaryOperator | 表示上产生一个double值结果的单个double值操作数的操作。 |
Function | 表示接受一个参数,并产生一个结果的函数。 |
IntConsumer | 表示接受单个int值的参数并没有返回结果的操作。 |
IntFunction | 表示接受一个int值参数,并产生一个结果的函数。 |
IntPredicate | 表示一个整数值参数谓词(布尔值函数)。 |
IntSupplier | 代表整型值的结果的提供者。 |
IntToLongFunction | 表示接受一个int值参数,并产生一个long值结果的函数。 |
IntUnaryOperator | 表示产生一个int值结果的单个int值操作数的运算。 |
LongBinaryOperator | 表示在两个long值操作数的操作,并产生一个ObjLongConsumer值结果。 |
LongFunction | 表示接受long值参数,并产生一个结果的函数。 |
LongPredicate | 代表一个long值参数谓词(布尔值函数)。 |
LongSupplier | 表示long值结果的提供者。 |
LongToDoubleFunction | 表示接受double参数,并产生一个double值结果的函数。 |
LongToIntFunction | 表示接受long值参数,并产生一个int值结果的函数。 |
LongUnaryOperator | 表示上产生一个long值结果单一的long值操作数的操作。 |
ObjDoubleConsumer | 表示接受对象值和double值参数,并且没有返回结果的操作。 |
ObjIntConsumer | 表示接受对象值和整型值参数,并返回没有结果的操作。 |
ObjLongConsumer | 表示接受对象值和整型值参数,并返回没有结果的操作。 |
ObjLongConsumer | 表示接受对象值和double值参数,并且没有返回结果的操作。 |
ObjIntConsumer | 表示接受对象值和整型值参数,并返回没有结果的操作。 |
ObjLongConsumer | 表示接受对象的值和long值的说法,并没有返回结果的操作。 |
Predicate | 代表一个参数谓词(布尔值函数)。 |
Supplier | 表示一个提供者的结果。 |
ToDoubleBiFunction | 表示接受两个参数,并产生一个double值结果的功能。 |
ToDoubleFunction | 代表一个产生一个double值结果的功能。 |
ToIntBiFunction | 表示接受两个参数,并产生一个int值结果的函数。 |
ToIntFunction | 代表产生一个int值结果的功能。 |
ToLongBiFunction | 表示接受两个参数,并产生long值结果的功能。 |
ToLongFunction | 代表一个产生long值结果的功能。 |
UnaryOperator | 表示上产生相同类型的操作数的结果的单个操作数的操作。 |
Consumer :有 一个 输入 无输出 函数接口
Product product=new Product();
//类名+静态方法 有一个输入T, 没有输出
Consumer consumer1 = Product->Product.nameOf(product);//lambda
consumer1.accept(product);
Consumer consumer = Product::nameOf;//方法引用
consumer.accept(product);
Funtion
//对象+方法 一个输入T 一个输出R
Function<Integer, Integer> function = product::reduceStock;
System.out.println("剩余库存:" + function.apply(10));
//带参数的构造函数
Function<Integer,Product> function1=Product::new;
System.out.println("新对象:" +function1.apply(200));
Predicate : 一个输入T, 一个输出 Boolean
//Predicate 一个输入T 一个输出Boolean
Predicate predicate= i -> product.isEnough(i);//lambda
System.out.println("库存是否足够:"+predicate.test(100));
Predicate predicate1= product::isEnough;//方法引用
System.out.println("库存是否足够:"+predicate1.test(100));
UnaryOperator :一元操作符 ,输入输出都是T
//一元操作符 输入和输出T
UnaryOperator integerUnaryOperator =product::reduceStock;
System.out.println("剩余库存:" + integerUnaryOperator.apply(20));
IntUnaryOperator intUnaryOperator = product::reduceStock;
System.out.println("剩余库存:" + intUnaryOperator.applyAsInt(30));
Supplier : 没有输入 只有输出
//无参数构造函数
Supplier supplier = Product::new;
System.out.println("创建新对象:" + supplier.get());
Supplier supplier1=()->product.getStock();
System.out.println("剩余库存:" + supplier1.get());
BiFunction: 二元操作符 两个输入
//类名+方法
BiFunction<Product, Integer, Integer> binaryOperator = Product::reduceStock;
System.out.println(" 剩余库存(BiFunction):" + binaryOperator.apply(product, 10));
BinaryOperator :二元操作符,二个输入,一个输出
//BinaryOperator binaryOperator1=(x,y)->product.reduceStock(x,y);
BinaryOperator binaryOperator1=product::reduceStock;
System.out.println(" 剩余库存(BinaryOperator):" +binaryOperator1.apply(product.getStock(),10));
just():
可以指定序列中包含的全部元素。创建出来的Flux序列在发布这些元素之后会自动结束
fromArray(),fromIterable(),fromStream():
可以从一个数组,Iterable对象或Stream对象中穿件Flux对象
empty():
创建一个不包含任何元素,只发布结束消息的序列
error(Throwable error):
创建一个只包含错误消息的序列
never():
传建一个不包含任务消息通知的序列
range(int start, int count):
创建包含从start起始的count个数量的Integer对象的序列
interval(Duration period)和interval(Duration delay, Duration period):
创建一个包含了从0开始递增的Long对象的序列。其中包含的元素按照指定的间隔来发布。除了间隔时间之外,还可以指定起始元素发布之前的延迟时间
intervalMillis(long period)和intervalMillis(long delay, long period):
与interval()方法相同,但该方法通过毫秒数来指定时间间隔和延迟时间
例子
Flux.just("Hello", "World").subscribe(System.out::println);
Flux.fromArray(new Integer[]{1, 2, 3}).subscribe(System.out::println);
Flux.empty().subscribe(System.out::println);
Flux.range(1, 10).subscribe(System.out::println);
Flux.interval(Duration.of(10, ChronoUnit.SECONDS)).subscribe(System.out::println);
Flux.intervalMillis(1000).subscirbe(System.out::println);
当序列的生成需要复杂的逻辑时,则应该使用generate()或create()方法。
generate()方法通过同步和逐一的方式来产生Flux序列。
序列的产生是通过调用所提供的的SynchronousSink对象的next(),complete()和error(Throwable)方法来完成的。
逐一生成的含义是在具体的生成逻辑中,next()方法只能最多被调用一次。
在某些情况下,序列的生成可能是有状态的,需要用到某些状态对象,此时可以使用
generate(Callable<S> stateSupplier, BiFunction<S, SynchronousSink<T>, S> generator),
其中stateSupplier用来提供初始的状态对象。
在进行序列生成时,状态对象会作为generator使用的第一个参数传入,可以在对应的逻辑中对改状态对象进行修改以供下一次生成时使用。
Flux.generate(sink -> {
sink.next("Hello");
sink.complete();
}).subscribe(System.out::println);
final Random random = new Random();
Flux.generate(ArrayList::new, (list, sink) -> {
int value = random.nextInt(100);
list.add(value);
sink.next(value);
if( list.size() ==10 )
sink.complete();
return list;
}).subscribe(System.out::println);
create()方法与generate()方法的不同之处在于所使用的是FluxSink对象。
FluxSink支持同步和异步的消息产生,并且可以在一次调用中产生多个元素。
Flux.create(sink -> {
for(int i = 0; i < 10; i ++)
sink.next(i);
sink.complete();
}).subscribe(System.out::println);
Mono类包含了与Flux类中相同的静态方法:just(),empty()和never()等。
除此之外,Mono还有一些独有的静态方法:
delay(Duration duration)和delayMillis(long duration)
:创建一个Mono序列,在指定的延迟时间之后,产生数字0作为唯一值ignoreElements(Publisher source)
:创建一个Mono序列,忽略作为源的Publisher中的所有元素,只产生消息justOrEmpty(Optional extends T> data)
和justOrEmpty(T data)
:从一个Optional对象或可能为null的对象中创建Mono。只有Optional对象中包含之或对象不为null时,Mono序列才产生对应的元素例子:
Mono.fromSupplier(() -> "Hello").subscribe(System.out::println);
Mono.justOrEmpty(Optional.of("Hello")).subscribe(System.out::println);
Mono.create(sink -> sink.success("Hello")).subscribte(System.out::println);
这两个操作符的作用是把当前流中的元素收集到集合中,并把集合对象作为流中的新元素。
Flux.range(1, 100).buffer(20).subscribe(System.out::println);
在进行收集时可以指定不同的条件:所包含的元素的最大数量或收集的时间间隔。
方法buffer()仅使用一个条件,而bufferTimeout()可以同时指定两个条件。
指定时间间隔时可以使用Duration对象或毫秒数,即使用bufferMillis()或bufferTimeoutMillis()两个方法。
除了元素数量和时间间隔外,还可以通过bufferUntil和bufferWhile操作符来进行收集。这两个操作符的参数时表示每个集合中的元素索要满足的条件的Predicate对象。
bufferUntil会一直收集直到Predicate返回true。
使得Predicate返回true的那个元素可以选择添加到当前集合或下一个集合中;bufferWhile则只有当Predicate返回true时才会收集。一旦为false,会立即开始下一次收集。
Flux.intervalMillis(100).bufferMillis(1001).take(2).toStream().forEach(System.out::println);
Flux.range(1, 10).bufferUntil(i -> i%2 == 0).subscribe(System.out::println);
Flux.range(1, 10).bufferWhile(i -> i%2 == 0).subscribe(System.out::println);
对流中包含的元素进行过滤,只留下满足Predicate指定条件的元素。
Flux.range(1, 10).filter(i -> i%2 == 0).subscribe(System.out::println);
zipWith操作符把当前流中的元素与另一个流中的元素按照一对一的方式进行合并。在合并时可以不做任何处理,由此得到的是一个元素类型为Tuple2的流;也可以通过一个BiFunction函数对合并的元素进行处理,所得到的流的元素类型为该函数的返回值。
Flux.just("a", "b").zipWith(Flux.just("c", "d")).subscribe(System.out::println);
Flux.just("a", "b").zipWith(Flux.just("c", "d"), (s1, s2) -> String.format("%s-%s", s1, s2)).subscribe(System.out::println);
take系列操作符用来从当前流中提取元素。提取方式如下:
take(long n),take(Duration timespan)和takeMillis(long timespan):按照指定的数量或时间间隔来提取
takeLast(long n):提取流中的最后N个元素
takeUntil(Predicate super T> predicate) :提取元素直到Predicate返回true
takeWhile(Predicate super T> continuePredicate):当Predicate返回true时才进行提取
takeUntilOther(Publisher> other):提取元素知道另外一个流开始产生元素
Flux.range(1, 1000).take(10).subscribe(System.out::println);
Flux.range(1, 1000).takeLast(10).subscribe(System.out::println);
Flux.range(1, 1000).takeWhile(i -> i < 10).subscribe(System.out::println);
Flux.range(1, 1000).takeUntil(i -> i == 10).subscribe(System.out::println);
reduce和reduceWith操作符对流中包含的所有元素进行累计操作,得到一个包含计算结果的Mono序列。累计操作是通过一个BiFunction来表示的。在操作时可以指定一个初始值。若没有初始值,则序列的第一个元素作为初始值。
Flux.range(1, 100).reduce((x, y) -> x + y).subscribe(System.out::println);
Flux.range(1, 100).reduceWith(() -> 100, (x + y) -> x + y).subscribe(System.out::println);
merge和mergeSequential操作符用来把多个流合并成一个Flux序列。merge按照所有流中元素的实际产生序列来合并,而mergeSequential按照所有流被订阅的顺序,以流为单位进行合并。
Flux.merge(Flux.intervalMillis(0, 100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println);
Flux.mergeSequential(Flux.intervalMillis(0, 100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println);
flatMap和flatMapSequential操作符把流中的每个元素转换成一个流,再把所有流中的元素进行合并。flatMapSequential和flatMap之间的区别与mergeSequential和merge是一样的。
Flux.just(5, 10).flatMap(x -> Flux.intervalMillis(x * 10, 100).take(x)).toStream().forEach(System.out::println);
concatMap操作符的作用也是把流中的每个元素转换成一个流,再把所有流进行合并。concatMap会根据原始流中的元素顺序依次把转换之后的流进行合并,并且concatMap堆转换之后的流的订阅是动态进行的,而flatMapSequential在合并之前就已经订阅了所有的流。
Flux.just(5, 10).concatMap(x -> Flux.intervalMillis(x * 10, 100).take(x)).toStream().forEach(System.out::println);
combineLatest操作符把所有流中的最新产生的元素合并成一个新的元素,作为返回结果流中的元素。只要其中任何一个流中产生了新的元素,合并操作就会被执行一次,结果流中就会产生新的元素。
Flux.combineLatest(Arrays::toString, Flux.intervalMillis(100).take(5), Flux.intervalMillis(50, 100).take(5)).toStream().forEach(System.out::println);
当需要处理Flux或Mono中的消息时,可以通过subscribe方法来添加相应的订阅逻辑。
在调用subscribe方法时可以指定需要处理的消息类型。
Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).subscribe(System.out::println, System.err::println);
Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).onErrorReturn(0).subscribe(System.out::println);
第2种可以通过switchOnError()方法来使用另外的流来产生元素。
Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).switchOnError(Mono.just(0)).subscribe(System.out::println);
第三种是通过onErrorResumeWith()方法来根据不同的异常类型来选择要使用的产生元素的流。
Flux.just(1, 2).concatWith(Mono.error(new IllegalArgumentException())).onErrorResumeWith(e -> {
if(e instanceof IllegalStateException)
return Mono.just(0);
else if(e instanceof IllegalArgumentException)
return Mono.just(-1);
return Mono.epmty();
}).subscribe(System,.out::println);
当出现错误时还可以使用retry操作符来进行重试。重试的动作是通过重新订阅序列来实现的。在使用retry操作时还可以指定重试的次数。
Flux.just(1, 2).concatWith(Mono.error(new IllegalStateException())).retry(1).subscrible(System.out::println);
StepVerifier的作用是可以对序列中包含的元素进行逐一验证。通过StepVerifier.create()方法对一个流进行包装之后再进行验证。expectNext()方法用来声明测试时所期待的流中的下一个元素的值,而verifyComplete()方法则验证流是否正常结束。verifyError()来验证流由于错误而终止。
StepVerifier.create(Flux.just(a, b)).expectNext("a").expectNext("b").verifyComplete();
使用StepVerifier.withVirtualTime()方法可以创建出使用虚拟时钟的SteoVerifier。通过thenAwait(Duration)方法可以让虚拟时钟前进。
StepVerifier.withVirtualTime(() -> Flux.interval(Duration.ofHours(4), Duration.ofDays(1)).take(2))
.expectSubscription()
.expectNoEvent(Duration.ofHours(4))
.expectNext(0L)
.thenAwait(Duration.ofDays(1))
.expectNext(1L)
.verifyComplete();
TestPublisher的作用在于可以控制流中元素的产生,甚至是违反反应流规范的情况。通过create()方法创建一个新的TestPublisher对象,然后使用next()方法来产生元素,使用complete()方法来结束流。
final TestPublisher<String> testPublisher = TestPublisher.creater();
testPublisher.next("a");
testPublisher.next("b");
testPublisher.complete();
StepVerifier.create(testPublisher)
.expectNext("a")
.expectNext("b")
.expectComplete();
在调试模式启用之后,所有的操作符在执行时都会保存额外的与执行链相关的信息。当出现错误时,这些信息会被作为异常堆栈信息的一部分输出。
Hooks.onOperator(providedHook -> providedHook.operatorStacktrace());
也可以通过checkpoint操作符来对特定的流处理链来启用调试模式。
Flux.just(1, 0).map(x -> 1/x).checkpoint("test").subscribe(System.out::println);
可以通过添加log操作把流相关的事件记录在日志中,
Flux.range(1, 2).log("Range").subscribe(System.out::println);
冷序列的含义是不论订阅者在何时订阅该序列,总是能收到序列中产生的全部消息。
热序列是在持续不断的产生消息,订阅者只能获取到在其订阅之后产生的消息。
final Flux<Long> source = Flux.intervalMillis(1000).take(10).publish.autoConnect();
source.subscribe();
Thread.sleep(5000);
source.toStream().forEach(System.out::println);
//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,然后通过合并将这些内部Publisher,最终扁平化为单个Flux 。
Flux可能包含N个元素,所以flatMap是一对多的转换。
Flux 将各个 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);
}
本节的内容来自 Reacto 3 参考文档——如何选择操作符。
如果一个操作符是专属于
Flux
或Mono
的,那么会给它注明前缀。
公共的操作符没有前缀。如果一个具体的用例涉及多个操作符的组合,这里以方法调用的方式展现,
会以一个点(.)开头,并将参数置于圆括号内,比如:.methodCall(parameter)
。
Mono ,是指最多只能触发(emit) (事件)一次。
Mono 对应于 RxJava 库的 Single 和 Maybe 类型或者是java的Optional。
因此一个异步任务,如果只是想要在完成时给出完成信号,就可以使用 Mono
。
@Test
public void testMonoBasic(){
Mono.fromSupplier(() -> "Hello").subscribe(System.out::println);
Mono.justOrEmpty(Optional.of("Hello")).subscribe(System.out::println);
Mono.create(sink -> sink.success("Hello")).subscribe(System.out::println);
}
Optional
:Mono#justOrEmpty(Optional)
null
的 T:Mono#justOrEmpty(T)
Flux 相当于一个 RxJava Observable,能够发出 0~N 个数据项,然后(可选地)completing 或 erroring。Flux处理多个数据项作为stream。
@Test
public void testBasic(){
Flux.just("Hello", "World").subscribe(System.out::println);
Flux.fromArray(new Integer[] {1, 2, 3}).subscribe(System.out::println);
Flux.empty().subscribe(System.out::println);
Flux.range(1, 10).subscribe(System.out::println);
Flux.interval(Duration.of(10, ChronoUnit.SECONDS)).subscribe(System.out::println);
}
使用 Mono#fromSupplier
或用 defer
包装 just
@Test
public void defer(){
//声明阶段创建DeferClass对象
Mono<Date> m1 = Mono.just(new Date());
Mono<Date> m2 = Mono.defer(()->Mono.just(new Date()));
m1.subscribe(System.out::println);
m2.subscribe(System.out::println);
//延迟5秒钟
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
m1.subscribe(System.out::println);
m2.subscribe(System.out::println);
}
T
,Flux#just(T...)
Flux#fromArray
Flux#fromIterable
Flux#range
Stream
提供给每一个订阅:Flux#fromStream(Supplier)
Supplier
:Mono#fromSupplier
Mono#fromCallable
,Mono#fromRunnable
CompletableFuture
:Mono#fromFuture
empty
Throwable
:error(Supplier)
never
defer
using
Flux#generate
Flux#create
Mono#create
也类似的,只不过只能发一个)map
cast
Flux#index
flatMap
+ 使用一个工厂方法handle
flatMap
+ 一个异步的返回类型为 Publisher
的方法Mono.empty()
Flux#flatMapSequential
(对每个元素的异步任务会立即执行,但会将结果按照原序列顺序排序)Mono#flatMapMany
Flux#startWith(T...)
Flux#concatWith(T...)
Flux
转化为集合(一下都是针对 Flux
的)
collectList
,collectSortedList
collectMap
,collectMultiMap
collect
count
reduce
scan
all
any
hasElements
hasElement
Flux#concat
或 .concatWith(other)
Flux#concatDelayError
Flux#mergeSequential
Flux#merge
/ .mergeWith(other)
Flux#zip
/ Flux#zipWith
Tuple2
:Mono#zipWith
Mono#zip
Mono
:Mono#and
Mono
:Mono#when
Flux#zip
Flux#combineLatest
Flux#first
,Mono#first
,mono.or(otherMono).or(thirdMono)
,flux.or(otherFlux).or(thirdFlux)
flatMap
,不过“喜新厌旧”):switchMap
switchOnNext
repeat
Flux.interval(duration).flatMap(tick -> myExistingPublisher)
defaultIfEmpty
switchIfEmpty
ignoreElements
Mono
来表示序列已经结束:then
thenEmpty
Mono
:Mono#then(mono)
Mono#thenReturn(T)
Flux
:thenMany
Mono#delayUntilOther
Mono#delayUntil(Function)
expand(Function)
expandDeep(Function)
doOnNext
Flux#doOnComplete
,Mono#doOnSuccess
doOnError
doOnCancel
doOnSubscribe
doOnRequest
doAfterTerminate
Signal
):Flux#doOnEach
doFinally
log
single
对象:doOnEach
dematerialize
log
filter
filterWhen
ofType
ignoreElements
Flux#distinct
Flux#distinctUntilChanged
Flux#take(Duration)
Mono
中返回:Flux#next()
request(N)
而不是取消:Flux#limitRequest(long)
Flux#takeLast
Flux#takeUntil
(基于判断条件),Flux#takeUntilOther
(基于对 publisher 的比较)Flux#takeWhile
Flux#elementAt
.takeLast(1)
Flux#last()
Flux#last(T)
Flux#skip(Duration)
Flux#skipLast
Flux#skipUntil
(基于判断条件),Flux#skipUntilOther
(基于对 publisher 的比较)Flux#skipWhile
sampleFirst
Flux#sample(Publisher)
Flux#sampleTimeout
(每一个元素会触发一个 publisher,如果这个 publisher 不被下一个元素触发的 publisher 覆盖就发出这个元素)Flux#single()
Flux#single(T)
Flux#singleOrEmpty
error
…
Flux
:.concat(Flux.error(e))
Mono
:.then(Mono.error(e))
timeout
error(Supplier)
error
onErrorReturn
Flux
或 Mono
:onErrorResume
.onErrorMap(t -> new RuntimeException(t))
doFinally
using
工厂方法onErrorReturn
Publisher
:Flux#onErrorResume
和 Mono#onErrorResume
retry
retryWhen
IllegalStateException
:Flux#onBackpressureError
Flux#onBackpressureDrop
Flux#onBackpressureLatest
Flux#onBackpressureBuffer
Flux#onBackpressureBuffer
带有策略 BufferOverflowStrategy
Tuple2
…
elapsed
timestamp
timeout
Flux#interval
0
:static Mono.delay
.Mono#delayElement
,Flux#delayElements
delaySubscription
Flux
拆分为一个 Flux>
:
window(int)
window(int, int)
window(Duration)
window(Duration, Duration)
windowTimeout(int, Duration)
windowUntil
cutBefore
变量):.windowUntil(predicate, true)
windowWhile
(不满足条件的元素会被丢弃)window(Publisher)
,windowWhen
Flux
的元素拆分到集合…
List
:buffer(int, int)
buffer(Duration, Duration)
bufferTimeout(int, Duration)
.bufferUntil(predicate, true)
bufferWhile(Predicate)
buffer(Publisher)
,bufferWhen
buffer(int, Supplier)
Flux
中具有共同特征的元素分组到子 Flux:groupBy(Function)
(注意返回值是 Flux>
,每一个 GroupedFlux
具有相同的 key 值 K
,可以通过 key()
方法获取)。8)回到同步的世界
Flux
,我想:
Flux#blockFirst
Flux#blockFirst(Duration)
Flux#blockLast
Flux#blockLast(Duration)
Iterable
:Flux#toIterable
Stream
:Flux#toStream
Mono
,我想:
Mono#block
Mono#block(Duration)
CompletableFuture
:Mono#toFuture
在响应式流中,错误(error)是终止(terminal)事件。
当有错误发生时,它会导致流序列停止, 并且错误信号会沿着操作链条向下传递,直至遇到定义的 Subscriber 及其 onError 方法。
在 try-catch 代码块中处理异常的几种方法。常见的包括如下几种:
以上所有这些在 Reactor 都有等效的 操作符处理方式。
与第 (1) 条(捕获并返回一个静态的缺省值)对应的是 onErrorReturn:
Flux.just(10)
.flatMap(this::function)
.onErrorReturn("Error");//返回一个静态的缺省值
根据错误类型返回对应值
Flux.just(10)
.flatMap(this::function)
.onErrorReturn(e -> e.getMessage().equals("boom-1"), "Error-1");
与第 (2、3、4) 条(捕获并执行一个异常处理方法)对应的是 onErrorResume
Flux.just(10)
.flatMap(m -> function(k).onErrorResume(e -> handleErr(k)));
Flux.just(10)
.flatMap(m -> function(k).onErrorResume(e -> errFunc(k)));
Flux.just(10)
.flatMap(m -> function(k)
.onErrorResume(e -> Flux.error(new Exception(k)))
);
对应第 (5) 条(捕获,记录错误日志,并继续抛出)
Flux.just(10)
.flatMap(k -> function(k))
.doOnError(e ->
log.error(e.getLocalizedMessage(), e);
});
对应(6)doFinally 在序列终止(无论是 onComplete、onError还是取消)的时候被执行, 并且能够判断是什么类型的终止事件
Flux.just(10)
.doFinally(type -> {
if (type == SignalType.CANCEL){
log.info("a log");
}
})
Reactor被视为与并发无关的。
也就是说,获得Flux或Mono并不一定意味着它在专用线程中运行。
取而代之的是,大多数Operator会继续在执行前一个Operator的线程中工作。
除非指定,否则最顶层的Operator(源)本身运行在进行subscribe()调用的线程上。
先分享一个案例:
在进行DB这种耗时的操作时,我们不希望在IO线程上执行,而是在专用的线程池里边执行,从而使得IO线程不会阻塞,。
来看看下面的代码:
//获得所有的用户
public Mono<ServerResponse> getAllUser(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
代码中没有使用subscriptOn或者publishOn操作符,userRepository.findAll() 会使后续的操作符都在 lettuce-1 线程(IO)线程中执行,所以, 并且整个流的执行都是串行的,
我们需要将查询过程改为异步查询。
Project Reactor中的调度程序(Schedulers)用于指示多线程(multi-threading)。
某些运算符具有将Scheduler作为参数的变体。 这些指示操作员在特定的调度程序上执行其部分或全部工作。
Project Reactor附带了一组预配置的调度程序(Schedulers),都可以通过Schedulers
类进行访问:
从java.util.concurrent.Executor创建调度程序
不要将计算调度程序用于I/O。
调度程序可以通过以下几种不同的方式执行发布者:
subscribeOn(Scheduler)
publishOn(Scheduler)
如果没有其他说明,默认情况下,诸如buffer
, replay
, skip
, delay
, parallel
等操作符将使用调度程序。
如果需要,所有列出的运算符都允许你传入自定义调度程序。 大多数时候都使用默认值是一个好主意。
如果希望在特定的调度程序上执行订阅链,请使用subscribeOn()
运算符。
该代码在未设置调度程序的情况下在主线程上执行:
Flux.just("Ben", "Michael", "Mark").flatMap(key -> {
System.out.println("Map 1: " + key + " (" + Thread.currentThread().getName() + ")");
return Flux.just(key);
}
).flatMap(value -> {
System.out.println("Map 2: " + value + " (" + Thread.currentThread().getName() + ")");
return Flux.just(value);
}
).subscribe();
该示例打印以下行:
Map 1: Ben (main)
Map 2: Ben (main)
Map 1: Michael (main)
Map 2: Michael (main)
Map 1: Mark (main)
Map 2: Mark (main)
此示例显示了添加到流中的subscribeOn()
方法(在哪里添加都无所谓):
Flux.just("Ben", "Michael", "Mark").flatMap(key -> {
System.out.println("Map 1: " + key + " (" + Thread.currentThread().getName() + ")");
return Flux.just(key);
}
).flatMap(value -> {
System.out.println("Map 2: " + value + " (" + Thread.currentThread().getName() + ")");
return Flux.just(value);
}
).subscribeOn(Schedulers.parallel()).subscribe();
该示例的输出显示了subscribeOn()
的效果。
你可以看到Publisher在同一线程上执行,但在计算线程池上执行:
Map 1: Ben (parallel-1)
Map 2: Ben (parallel-1)
Map 1: Michael (parallel-1)
Map 2: Michael (parallel-1)
Map 1: Mark (parallel-1)
Map 2: Mark (parallel-1)
如果将相同的代码应用于Lettuce,你将注意到执行第二个flatMap()
的线程有所不同:
Flux.just("Ben", "Michael", "Mark").flatMap(key -> {
System.out.println("Map 1: " + key + " (" + Thread.currentThread().getName() + ")");
return commands.set(key, key);
}).flatMap(value -> {
System.out.println("Map 2: " + value + " (" + Thread.currentThread().getName() + ")");
return Flux.just(value);
}).subscribeOn(Schedulers.parallel()).subscribe();
该示例打印以下行:
Map 1: Ben (parallel-1)
Map 1: Michael (parallel-1)
Map 1: Mark (parallel-1)
Map 2: OK (lettuce-nioEventLoop-3-1)
Map 2: OK (lettuce-nioEventLoop-3-1)
Map 2: OK (lettuce-nioEventLoop-3-1)
与独立示例有两点不同:
flatMap()
转换输出netty EventLoop线程名称这是因为默认情况下,Lettuce发布者是在netty EventLoop线程上执行和完成的。publishOn
指示发布者在特定的Scheduler上调用其观察者的nNext
, onError
和onCompleted
方法。
在这里,顺序很重要:
Flux.just("Ben", "Michael", "Mark").flatMap(key -> {
System.out.println("Map 1: " + key + " (" + Thread.currentThread().getName() + ")");
return commands.set(key, key);
}).publishOn(Schedulers.parallel()).flatMap(value -> {
System.out.println("Map 2: " + value + " (" + Thread.currentThread().getName() + ")");
return Flux.just(value);
}).subscribe();
publishOn()
调用之前的所有操作均在main中执行,而调度器中的以下所有操作均在其中执行:
Map 1: Ben (main)
Map 1: Michael (main)
Map 1: Mark (main)
Map 2: OK (parallel-1)
Map 2: OK (parallel-1)
Map 2: OK (parallel-1)
调度程序(Schedulers)允许直接调度操作。 有关更多信息,请参考Project Reactor文档。
Reactor提供了两种在流中切换执行上下文(或Scheduler)的方式:
我们可以通过这两种方式达到异步执行的目的
publishOn :
此运算符影响线程上下文,它下面的链中的其余运算符将在其中执行,直到新出现的publishOn。
void demo() {
final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
.map(log())//1
.publishOn(Schedulers.parallel())//2
.map(log())//3
.publishOn(Schedulers.elastic())//4
.flatMap(logOfMono());//5
flux.subscribe();
}
上面代码中:
subscribeOn :
整个流在指定的Scheduler的Scheduler.Worker上运行 ,直至出现publishOn ,publishOn后续的操作符由 publishOn 决定执行上下文。
void demo() {
final Flux<Long> flux = Flux.interval(Duration.of(100, ChronoUnit.MILLIS))
.map(log())//1
.publishOn(Schedulers.parallel())
.map(log())//2
.subscribeOn(Schedulers.elastic())
.flatMap(logOfMono());
flux.subscribe();
}
上面代码中:
这样我们就可以异步的去执行方法了。
Flux依次发出了1,2,3三个元素 无论是subscribeOn、还是publishOn,经过flatMap 都会在 parallel-1 线程上执行,也就是说,Flux中的所有元素只是从主线程发出,在另一个线程中执行。
在调用一些阻塞方法时(rpc、redis、io),我们期望每个元素经过 flatMap 中时可以运行在不同线程上(串行-> 并发),应该怎么做?
将Flux转为ParallelFlux,使用runOn来指明需要的线程池
要获得ParallelFlux,可以在Flux 上使用parallel()运算符。
为了告诉ParallelFlux在哪里运行每个元素,必须使用runOn(Scheduler)。
如果并行处理后,您想恢复到“正常”状态 Flux并以顺序方式应用运算符链的其余部分,可以使用sequential()
void demo() {
final Flux<Long> flux = Flux.fromIterable(Lists.newArrayList(3L, 1L, 2L))
.parallel().runOn(Schedulers.elastic())
.flatMap(logOfMono())
.sequential();
flux.subscribe();
}
fluxMap中调用的方法需要在内部通过publishOn表明执行上下文。
void demo() {
Flux.fromIterable(Lists.newArrayList(3L, 1L, 2L))
.flatMap(this::blockFunction)
.subscribe();
}
//通过publishOn 来表明 blockFunction() 的上下文
Mono<Long> blockFunctionAsync(Long i) {
return Mono.just(i).publishOn(Schedulers.elastic()).flatMap(this::blockFunction);
}
Mono<Long> blockFunction(Long i) {
Thread.sleep(i * 1000);
return Mono.just(i);
}
Mono.fromFuture(CompletableFuture.supplyAsync(() -> blockFunction()));
Mono.fromFuture() 创建一个Mono ,需要提供一个 CompletableFuture 产生它的值。
CompletableFuture.supplyAsync() 返回一个 CompletableFuture,它将在ForkJoinPool.commonPool() 上运行任务,异步完成。
ForkJoinPool一个全局线程池,主要应用于计算密集型的场景。
//自定义线程池
final Executor executor = new ThreadPoolExecutor(...);
//创建Mono、Flux时 指明上下文
Mono.create(sink -> executor.execute(() -> {
sink.success(blockFunction());
}));
Mono<User> userMono = Mono.create(sink -> bizPool.execute(() -> {
sink.success( jpaEntityService.selectOne(dto.getUserId()));
}));
目前我们推荐使用 这种方法来创建Mono、Flux来实现异步
这样做的好处:
建议查询业务场景 采取合适的方法:
方法1 :
ParallelFlux 不能保证上游元素 返回顺序,不满足多个 sql 有序执行,或者 上游 元素有序执行的 场景。
方法2:
Reactor对新手来说有一定理解成本,在调用一个返回值为 Publisher 的类型的方法时,不点进去无法知道这是同步方法还是异步方法,对于调用者存在心智负担。
方法3.1:
无法指定自定义线程池
方法3.2:
这样做的好处:
简单来说,Webflux 是响应式编程的框架,与其对等的概念是 SpringMVC。
两者的不同之处在于 Webflux 框架是异步非阻塞的,其可以通过较少的线程处理高并发请求。
Webflux 的框架底层采用了 Reactor响应式编程框架以及 Netty,关于这两部分内容可以参看我之前的3高基础书籍:
《Java高并发核心编程 卷1 加强版》
作为一个异步框架来说,必须保证整个程序链中的每一步都是异步操作,如果在某一步出现了同步阻塞(如等待数据库 IO),则整个程序还是回出现阻塞的问题。
既适合IO密集型、磁盘IO密集、网络IO密集等服务场景,也适用于高并发、高吞吐业务场景的响应式改造。
比如微服务网关,就可以使用webflux技术来显著的提升网关对下游服务的吞吐量,spring cloud gateway就使用了webflux这门技术
spring.io 官网有句醒目的话是:
BUILD ANYTHING WITH SPRING BOOT
Spring Boot (Boot 顾名思义,是引导的意思)框架是用于简化 Spring 应用从搭建到开发的过程。
应用开箱即用,只要通过一个指令,包括命令行 java -jar 、SpringApplication 应用启动类 、 Spring Boot Maven 插件等,就可以启动应用了。
另外,Spring Boot 强调只需要很少的配置文件,所以在开发生产级 Spring 应用中,让开发变得更加高效和简易。
目前,Spring Boot 2.x 包括 WebFlux。
Spring Boot Webflux 就是基于 Reactive Streams 实现的。Spring Boot 2.0 包括一个新的 spring-webflux 模块。
所以,了解 WebFlux,首先了解下什么是 Reactive Streams。
Reactive Streams 是 JVM 中面向流的库标准和规范:
Backpressure(背压)
背压是一种常用策略,使得发布者拥有无限制的缓冲区存储元素,用于确保发布者发布元素太快时,不会去压制订阅者。
响应式 API
Reactor 框架是Reactive Streams 的一个实现框架,也是 Spring Boot Webflux 响应库依赖,通过 Reactor 并与其他响应库交互。
Webflux 提供了 两种响应式 API:Mono 和 Flux。
Webflux 的处理流程是:一般是将 Publisher 作为输入,在框架内部转换成 Reactor 类型并处理逻辑,然后返回 Flux 或 Mono 作为输出。
Spring Boot Webflux 有两种编程模型实现,一种类似 Spring MVC 注解方式,另一种是使用其功能性端点方式。
Spring Boot 2.0 WebFlux 特性
常用的 Spring Boot 2.0 WebFlux 生产的特性如下:
还有对日志、Web、消息、测试及扩展等支持。
一图就很明确了,WebFlux 和 MVC 有交集,方便大家迁移。但是注意:
编程模型
Spring 5 web 模块包含了 Spring WebFlux 的 HTTP 抽象。类似 Servlet API , WebFlux 提供了 WebHandler API 去定义非阻塞 API 抽象接口。可以选择以下两种编程模型实现:
内嵌容器
跟 Spring Boot 大框架一样启动应用,但 WebFlux 默认是通过 Netty 启动,并且自动设置了默认端口为 8080。另外还提供了对 Jetty、Undertow 等容器的支持。
开发者自行在添加对应的容器 Starter 组件依赖,即可配置并使用对应内嵌容器实例。
但是要注意,必须是 Servlet 3.1+ 容器,如 Tomcat、Jetty;或者非 Servlet 容器,如 Netty 和 Undertow。
Netty优点
Spring Webflux模块包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST,HTML 和 WebSocket 交互等程序的支持。
一般来说,Spring MVC 用于同步处理,Spring Webflux 用于异步处理。
回顾一下,传统的以SpringMVC为代表的webmvc技术使用的是同步阻塞式IO模型
而Spring WebFlux是一个异步非阻塞式IO模型,可以用少量的容器线程支撑大量的并发访问,
所以Spring WebFlux可以提升吞吐量和伸缩性,但是接口的响应时间并不会缩短,
其处理结果还是得由worker线程处理完成之后在返回给请求
首先创建 maven 项目,在项目的 pom 文件中引入相应的依赖
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.3version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-r2dbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>dev.mikugroupId>
<artifactId>r2dbc-mysqlartifactId>
<version>0.8.2.RELEASEversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.24version>
dependency>
dependencies>
创建项目的启动类
@SpringBootApplication
public class WebfluxDemoApplication {
public static void main(String[] args) {
SpringApplication.run(WebfluxDemoApplication.class);
}
}
此时,我们就可以编写一个简单的 Controller 来感受一下 Webflux 框架异步相应的概念
package com.crazymaker.springcloud.reactive.user.info.controller;
import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.common.util.ThreadUtil;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import com.crazymaker.springcloud.reactive.user.info.service.impl.JpaEntityServiceImpl;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.util.concurrent.ExecutorService;
import java.util.function.Supplier;
import static com.crazymaker.springcloud.reactive.user.info.parallel.DBFunctionWrapper.dbFunWrapSafe;
/**
* Mono 和 Flux 适用于两个场景,即:
* Mono:实现发布者,并返回 0 或 1 个元素,即单对象。
* Flux:实现发布者,并返回 N 个元素,即 List 列表对象。
* 有人会问,这为啥不直接返回对象,比如返回 City/Long/List。
* 原因是,直接使用 Flux 和 Mono 是非阻塞写法,相当于回调方式。
* 利用函数式可以减少了回调,因此会看不到相关接口。这恰恰是 WebFlux 的好处:集合了非阻塞 + 异步
*/
@Slf4j
@Api(value = "用户信息、基础学习DEMO", tags = {"用户信息DEMO"})
@RestController
@RequestMapping("/api/user")
public class UserReactiveController {
static ExecutorService bizPool = ThreadUtil.getIoIntenseTargetThreadPool();
@ApiOperation(value = "回显测试", notes = "提示接口使用者注意事项", httpMethod = "GET")
@RequestMapping(value = "/hello")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "query", dataType = "string", dataTypeClass = String.class, name = "name", value = "名称", required = true)})
public Mono<RestOut<String>> hello(@RequestParam(name = "name") String name) {
log.info("方法 hello 被调用了");
return Mono.just(RestOut.succeed("hello " + name));
}
}
上述代码中,我们定义了一个异步响应的接口,启动程序调用相应接口,可以看到效果
Spring WebFlux不依赖于Servlet API,它可以运行在非Servlet容器Netty、Undertow和任何Servlet 3.1+的Servlet容器(Tomcat,Jetty)之上。
虽然Servlet 3.1提供了非阻碍I/O的API,但是有很多其它的API依然是同步或阻碍式的;这使得Spring需要重新构建完全基于异步和非阻碍式的运行环境。
Spring WebFlux最底层的组件是HttpHandler
,它用来适配不同的服务器引擎(Netty、Undertow、Tomcat、Jetty)。
在通过HttpHandler
消除了服务器引擎的异构行后,Spring WebFlux的API设计与Spring MVC是高度一致的。
Spring WebFlux与Spring MVC在概念上有对应的关系:
Spring WebFlux | Spring Web MVC |
---|---|
DispatcherHandler |
DispatcherServlet |
WebFilter |
Filter |
HttpMessageWriter HttpMessageReader |
HttpMessageConverter |
HandlerMapping |
HandlerMapping |
HandlerAdapter |
HandlerAdapter |
ServerHttpRequest ServerHttpResponse |
ServletRequest ServletResponse |
Spring WebFlux支持两种编程模型:
RequestMappingHandlerMapping
:映射请求与@RequestMapping
控制器类和方法;RequestMappingHandlerAdapter
:调用@RequestMapping
注解的方法。RouterFunctionMapping
:用来支持RouterFunction
;HandlerFunctionAdapter
:用来支持HandlerFunctions
。Spring Boot提供的自动配置主要有:
CodecsAutoConfiguration
:Spring WebFlux使用HttpMessageReader
和HttpMessageWriter
接口来转换HTTP请求和返回。本配置类为我们注册了CodecCustomizer
的Bean,默认使用Jackson2JsonEncoder
和Jackson2JsonDecoder
。ReactiveWebServerFactoryAutoConfiguration
:为响应式Web服务器进行自动配置。WebFluxAutoConfiguration
:使用等同于@EnableWebFlux
的配置开启WebFlux的支持。可通过WebFluxProperties
使用spring.webflux.*
来对WebFlux进行配置:spring:
webflux:
date-format: yyyy-MM-dd # 日期格式
static-path-pattern: /resouces/static/** # 静态资源目录
WebClientAutoConfiguration
:为WebClient
进行自动配置。Reactive Programming 作为观察者模式(Observer) 的延伸,不同于传统的命令编程方式( Imperative programming)同步拉取数据的方式,如迭代器模式(Iterator) 。
而是采用数据发布者同步或异步地推送到数据流(Data Streams)的方案。当该数据流(Data Steams)订阅者监听到传播变化时,立即作出响应动作。
在实现层面上,Reactive Programming 可结合函数式编程简化面向对象语言语法的臃肿性,屏蔽并发实现的复杂细节,提供数据流的有序操作,从而达到提升代码的可读性,以及减少 Bugs 出现的目的。
同时,Reactive Programming 结合背压(Backpressure)的技术解决发布端生成数据的速率高于订阅端消费的问题。
Project Reactor
提供了很多创建Mono/Flux的静态方法,而最常用的就是Mono#create方法,通过该方法能把以前命令式的程序转化为Reactive的编程方式。
众所周知,Reactive Programming是一种Pull-Push模型,其中Pull用于实现back-pressure,
Iterator 属于推模式(push-based),Reactive Flux/Mono 属于拉模式(pull-based)
下面以一个常见的Pull模型迭代器Iterator来说明如何将传统代码转为Reactive的代码。
//创建一个迭代器
Iterator it = Arrays.asList<>(1,2,3).iterator();
//使用迭代器
while(it.hasNext()) {
//模拟业务逻辑 —— 这里直接打印value
System.out.println(it.next());
}
上面是一个常见的迭代器使用方式,下面看看是如何将迭代器转换成Flux的:
@Test
public void fluxExample() throws InterruptedException {
//创建迭代器
Iterator it = Arrays.asList(1, 2, 3).iterator();
Flux<Integer> iteratorFlux = Flux.create(sink -> {
while (it.hasNext()) {
Integer data = (Integer) it.next();
sink.next(data); //利用FluxSink实现data的Push
}
sink.complete(); //发送结束的Signal
});
//进行订阅,进行业务逻辑操作
iteratorFlux.log().subscribe(System.out::println);
}
输出
23:29:15.727 [main] INFO reactor.Flux.Create.1 - onSubscribe(FluxCreate.BufferAsyncSink)
23:29:15.730 [main] INFO reactor.Flux.Create.1 - request(unbounded)
23:29:20.931 [main] INFO reactor.Flux.Create.1 - onNext(1)
1
23:29:20.931 [main] INFO reactor.Flux.Create.1 - onNext(2)
2
23:29:20.931 [main] INFO reactor.Flux.Create.1 - onNext(3)
3
23:29:20.932 [main] INFO reactor.Flux.Create.1 - onComplete()
Disconnected from the target VM, address: '127.0.0.1:58005', transport: 'socket'
Process finished with exit code 0
传统命令式编程除了Iterator的Pull模式外,通常还有Observable以及Callback这两种Push模式,下面分别举例讲讲这两种模式。
Observable原始代码举例:
Observable observable = new Observable() {
//需要重写Observable,默认是setChanged与notifyObservers分离,实现先提交再通知的效果
//这里为了简单起见,将通知与提交放在了一起
@Override
public void notifyObservers(Object arg) {
setChanged();
super.notifyObservers(arg);
}
};
Observer first = (ob,value) -> {
System.out.println("value is " + value);
};
observable.addObserver(first);
observable.notifyObservers("42");
// after some time, cancel observer to dispose resource
observable.deleteObserver(first);
MonoCreate的转化示例:
Mono<Object> observableMono = Mono.create(sink -> {
Observer first = (ob, value) -> {
sink.success(value);
};
observable.addObserver(first);
observable.notifyObservers("42");
sink.onDispose(() -> observable.deleteObserver(first));
});
observableMono.subscribe(v -> System.out.println("value is " + v));
@Test
public void callbackExample() throws InterruptedException {
ListeningExecutorService service = MoreExecutors.listeningDecorator(bizPool);
ListenableFuture future = service.submit(new Runnable() {
@Override
public void run() {
log.info("mydebug run, " + Thread.currentThread().getName() );
}
});
Futures.addCallback(future, new FutureCallback() {
@Override
public void onSuccess(Object result) {
log.info("mydebug onSuccess, " + Thread.currentThread().getName() );
}
@Override
public void onFailure(Throwable thrown) {
log.info("mydebug onFailure, " + Thread.currentThread() .getName() );
}
},bizPool);
Thread.sleep(10000);
}
MonoCreate的转化示例:
@Data
static class CallbackHandlerInner {
private MonoSink monoSink;
private FutureCallback<Object> responseCallback;
public CallbackHandlerInner(MonoSink monoSink) {
this.monoSink = monoSink;
responseCallback = new FutureCallback<Object>() {
@Override
public void onSuccess(@Nullable Object o) {
log.info(" .... 2: FutureCallback run, " + Thread.currentThread().getName());
monoSink.success(o);
}
@Override
public void onFailure(Throwable throwable) {
monoSink.error(throwable);
}
};
}
}
@Test
public void MonoCreateExample() throws InterruptedException {
ListeningExecutorService service = MoreExecutors.listeningDecorator(bizPool);
ListenableFuture future = service.submit(new Callable<Object>() {
@Override
public Object call() {
log.info(" .... 1: mydebug run, " + Thread.currentThread().getName());
return "async out";
}
});
Mono<Object> responseMono = Mono.create(monoSink -> {
// callback的处理类,并传入monoSink供使用
CallbackHandlerInner callbackHandler = new CallbackHandlerInner(monoSink);
Futures.addCallback(future, callbackHandler.getResponseCallback(), dbPool);
});
responseMono.subscribeOn(Schedulers.single())
.subscribe(out -> log.info(" .... 3: final out:, " + out.toString()));
ThreadUtil.sleepMilliSeconds(1111);
}
从前面已经可以看到,将传统代码转变为Reactive方式的关键是在于sink,
在创建Mono/FluxCreate的时候,Mono/Flux都会提供相应的sink给使用方来使用。
MonoSink相比FluxSink要简单的多,为了简单起见,我们从MonoSink来了解sink的运行原理。下面就来探探Mono下的MonoSink究竟到底是什么。
再深入MonoSink之前,我们先来看看MonoCreate是怎么使用MonoSink的,对于Reactor来说,所有的入口都是subscribe
方法,所以先来看看MonoCreate的subscribe方法:
public void subscribe(CoreSubscriber<? super T> actual) {
//1. 创建MonoSink实例,供MonoCreate来使用
//如变量名字emitter一样,MonoSink的作用其实就是信号的发射器(signal emitter)
DefaultMonoSink<T> emitter = new DefaultMonoSink<>(actual);
//2. emitter除了是sink外,也实现了subscription,供Subscriber使用
//这一步,调用Subscriber的onSubscribe方法,其内部则会调用subscription的request方法 (后续会重点说DefaultMonoSink的request方法)
actual.onSubscribe(emitter);
try {
//3. callback就是在Mono.create时候传入的Mono构造器
//此步骤即调用Mono构造器函数,并将sink传入
callback.accept(emitter);
}
catch (Throwable ex) {
emitter.error(Operators.onOperatorError(ex, actual.currentContext()));
}
}
从上面的源代码可以看出,整个MonoCreate订阅过程很简单,主要是分为三个步骤:
以上三个步骤是从整体视角来看的,我们再进一步进入DefaultMonoSink,以它的内部视角,来看看到底作为signal emitter的MonoSink做了些什么。
MonoSink内部主要有4个状态:
volatile int state; //初始默认状态0,即未调用Request且未赋值
static final int NO_REQUEST_HAS_VALUE = 1; //未调用Request但已经赋值
static final int HAS_REQUEST_NO_VALUE = 2; //调用了Request但还未赋值
static final int HAS_REQUEST_HAS_VALUE = 3; //调用了Request且已经赋值了
这三个状态主要取决于request和success(或者error)的调用时机,调用了request方法则会是HAS_REQUEST
,调用了success(或者error)方法则会是HAS_VALUE
,其中request方法调用是由Subscriber#onSubscribe调用的,success或者error则是由具体使用者来调用的,如Callback。
由于success或者error调用时机往往不可能确定(通常是异步的),所以才产生了上述4种状态。
以同步的角度思考,通常是先调用request然后再调用success或者error方法,其中success会对应调用Subscriber的onNext与onComplete方法,error方法则会调用对应的Subscriber#onError方法。但事情往往没这么简单,就如前面提到的,request方法与success/error方法是乱序的,很有可能在request的时候,success/error方法已经调用结束了。
为了解决这个问题,每个方法都引入了for-loop加CAS的多线程操作,变得相对复杂了,但只要知道其内部原理,再复杂的代码看起来就都有线索了,下面以request方法为例,来讲讲是MonoSink是如何解决多线程问题的。
public void request(long n) {
if (Operators.validate(n)) {
LongConsumer consumer = requestConsumer;
//1. 如果传入了requestConsumer,则调用
//requestConsumer是通过onRequest方法传入的
if (consumer != null) {
consumer.accept(n);
}
//2. 进入for loop来实现自旋
for (; ; ) {
int s = state;
//2.1 HAS_Request: 已经调用过了,直接退出
if (s == HAS_REQUEST_NO_VALUE || s == HAS_REQUEST_HAS_VALUE) {
return;
}
if (s == NO_REQUEST_HAS_VALUE) {
// 2.2 double check 是否已经有值
// 如果是,执行onNext/onComplete方法,并设置完成状态: HAS_REQUEST_HAS_VALUE
// 如果不是,double check失败,直接退出,说明有别的线程已经执行了该方法了
if (STATE.compareAndSet(this, s, HAS_REQUEST_HAS_VALUE)) {
try {
actual.onNext(value);
actual.onComplete();
}
finally {
//释放资源 - 具体调用的disposable对象由onDisposable方法赋值
disposeResource(false);
}
}
return;
}
//2.3 正常流程,值没有被赋值,设置为HAS_REQUEST_NO_VALUE
if (STATE.compareAndSet(this, s, HAS_REQUEST_NO_VALUE)) {
return;
}
}
}
}
MonoSink除了request、success、error方法外,还提供了几个回调函数,以供使用者使用,主要有:
//request的时候会被调用,获取request的数量N
MonoSink<T> onRequest(LongConsumer consumer);
//Subscriber调用subscription.cancel是会调用该Disposable方法
MonoSink<T> onCancel(Disposable d);
//与onCancel类似,区别是,除了onCancel方法,在onComplete以及onError也会调用该Disposable方法
MonoSink<T> onDispose(Disposable d);
这里简单讲一下Reactor的代码命名规范,对于回调函数都是以onXXX方式命名,注意调用该onXXX方式的时候,并不是直接调用,而只是传入该回调方法,待对应的事件信号发生时,才会真的被调用。
这也是声明式编程的一个特色,先声明再执行。
结论来自于,尼恩的实操,具体请参考下面的文章
全链路异步,让你的 SpringCloud 性能优化10倍+
原文:
https://blog.allegro.tech/2019/07/migrating-microservice-to-spring-webflux.html
响应式编程在这几个月内一直是许多会议演讲的热门话题。找到简单的代码示例和教程并将它们应用于allegro新项目是毫不费力的。
当需要从现有解决方案迁移时,特别是它是具有数百万用户和每秒数千个请求的生产服务时,事情变得有点复杂。
在本文中,我想 通过一个Allegro微服务的例子讨论从[Spring Web MVC到 [Spring WebFlux的迁移策略 。
我将展示一些常见的陷阱,以及生产中的性能指标如何受迁移的影响。
改变的动机
在详细探讨迁移策略之前,让我们先讨论其变更的动机。
其中一个由我的团队开发和维护的微服务,参与了2018年7月18日的重大Allegro停运(详见尸检))。
虽然我们的微服务不是问题的根本原因,但由于线程池饱和,一些实例也崩溃了。
临时修复是:增加线程池大小并减少外部服务调用的超时; 然而,这还不够。
临时解决方案仅略微提高了外部服务延迟的吞吐量和弹性。
我们决定转而使用非阻塞方法来彻底摆脱线程池作为并发的基础。
使用WebFlux的另一个动机是新项目,它使我们的微服务中的外部服务调用流程变得复杂。
无论复杂程度如何增加,我们都面临着保持代码库可维护性和可读性的挑战。
我们看到WebFlux比我们之前基于Java 8的解决方案(CompletableFuture可以模拟复杂的流程)更加友好。
每一种新兴技术都倾向于其炒作周期。
玩一种新的解决方案,特别是在生产环境中,仅仅因为它新鲜,有光泽和嗡嗡声
尝鲜可能导致沮丧,有时甚至是灾难性的后果。
每个软件供应商都想宣传他的产品,并说服客户使用它。
但是,WebFlux的官方 Pivotal 表现得非常负责任,密切关注那些最好不要迁移到WebFlux的场景。官方文件的第1.1.4部分详细介绍了这一点。
官方文件最重要的几点是:
如果您的服务中没有性能问题,或扩展性问题,建议不要迁移,而是找一个更好的地方来尝试WebFlux。
他们可以合作,但把阻塞式API迁移到响应式编程,不会有效率提升。
如果没有迁移恰当,一个阻塞调用可以锁定整个应用程序。
你应该在迁移过程中非常注意人为因素。
我们来谈谈性能。
对此有很多误解。反应Reactive并不意味着马上、自动大的提升性能,提升性能还有很多工作要做。WebFlux文档警告我们,以非阻塞方式执行操作需要做更多工作。
响应式闪耀点:等待其他服务响应不会阻塞线程。
因此,获得相同吞吐量所需的线程更少,线程越少意味着使用的内存越少。
始终建议检查独立来源以避免框架作者的偏见。
总而言之,迁移到WebFlux有四个指标如果符合则可行:
根据我们的迁移经验,我想介绍三阶段迁移策略。
为什么3个阶段?
如果我们谈论具有大型代码库的实时服务,每秒数千个请求和数百万用户, 从头开始重写是一个相当大的风险。
让我们看看如何在后续的小步骤中将应用程序从Spring Web MVC迁移到Spring WebFlux,从而实现从阻塞到非阻塞世界的平滑过渡。
通常,首先在系统的非关键部分尝试新技术是一种很好的做法。
响应式技术也不例外。这个阶段的想法是只要找到一个非关键特性功能,它又是被封装在一个阻塞方法调用中,那就将其重写为非阻塞风格。
让我们看看执行此阻塞方法的示例,该方法用于RestTemplate从外部服务检索结果。
Pizza getPizzaBlocking(int id) {
try {
return restTemplate.getForObject("http://localhost:8080/pizza/" + id, Pizza.class);
} catch (RestClientException ex) {
throw new PizzaException(ex);
}
}
我们从丰富的WebFlux功能集中选择一件事 - 反应式WebClient - 并使用它以非阻塞方式重写此方法:
Mono<Pizza> getPizzaReactive(int id) {
return webClient
.get()
.uri("http://localhost:8080/pizza/" + id)
.retrieve()
.bodyToMono(Pizza.class)
.onErrorMap(PizzaException::new);
}
现在是时候将我们的新方法与应用程序的其余部分连接起来了。
非阻塞方法返回Mono,但我们需要一个普通类型。
我们可以使用Mono.block()方法从中检索值。
Pizza getPizzaBlocking(int id) {
return getPizzaReactive(id).block();
}
最终,我们的方法仍在都塞等待。
但是,它内部使用了非阻塞库。
此阶段的主要目标是熟悉非阻塞API。这种更改对应用程序的其余部分是透明的,易于测试并可部署到生产环境中。
在使用WebClient转换一小段代码后,我们准备更进一步。
第二阶段的目标是将应用程序的关键路径转换为非阻塞 - 从HTTP客户端到处理外部服务响应的类,再到控制器。
在这个阶段,重要的是避免重写所有代码。
应用程序中较不重要的部分,例如没有外部调用或很少使用的部分,应该保持不变。
我们需要关注非阻塞方法揭示其优势的领域。
//parallel call to two services using Java8 CompletableFuture
Food orderFoodBlocking(int id) {
try {
return CompletableFuture.completedFuture(new FoodBuilder())
.thenCombine(CompletableFuture.supplyAsync(() -> pizzaService.getPizzaBlocking(id), executorService), FoodBuilder::withPizza)
.thenCombine(CompletableFuture.supplyAsync(() -> hamburgerService.getHamburgerBlocking(id), executorService), FoodBuilder::withHamburger)
.get()
.build();
} catch (ExecutionException | InterruptedException ex) {
throw new FoodException(ex);
}
}
//parallel call to two services using Reactor
Mono<Food> orderFoodReactive(int id) {
return Mono.just(new FoodBuilder())
.zipWith(pizzaService.getPizzaReactive(id), FoodBuilder::withPizza)
.zipWith(hamburgerService.getHamburgerReactive(id), FoodBuilder::withHamburger)
.map(FoodBuilder::build)
.onErrorMap(FoodException::new);
}
使用.subscribeOn()方法,可以轻松地将阻塞部分系统与非阻塞代码合并。
可以使用默认的Reactor调度器之一,或者我们自己创建并提供的线程池ExecutorService。
Mono<Pizza> getPizzaReactive(int id) {
return Mono.fromSupplier(() -> getPizzaBlocking(id))
.subscribeOn(Schedulers.fromExecutorService(executorService));
}
此外,只需对控制器进行少量更改即可 - 将返回类型更改Foo为Mono
或Flux
。
它甚至可以在Spring Web MVC中运行 - 您不需要将整个应用程序的堆栈更改为被动。
第2阶段的成功实施为我们提供了非阻塞方法的所有主要优点。是时候测量并检查我们的问题是否已解决。
我们可以在第2阶段之后做更多的事情。
我们可以重写代码中不太关键的部分,并使用Netty服务器而不是servlet。
我们也可以删除@Controller注释并将端点重写为函数风格,尽管这是因为风格和个人偏好去改写,而非性能的问题去改写。
这里的关键问题是:这些优势的成本是多少?
代码可以一直重构,并且通常定义“足够好”的点是很有挑战性的。
在我们的案例中,我们没有决定更进一步。重写整个代码库需要很多工作。
帕累托原则结果证明是有效的一次。
我们认为我们已经取得了显着的收益,而后续的收益也相对较高。
作为一般规则 - 当我们从头开始编写新服务时,获得WebFlux的所有特权是很好的。
另一方面,当我们重构现有(微)服务时,通常最好尽可能少地完成工作。
WebFlux初学者有时会忘记反应流往往尽可能地会惰加载。
由于缺少订阅,以下功能永远不会向控制台打印任何内容:
Food orderFood(int id) {
FoodBuilder builder = new FoodBuilder().withPizza(new Pizza("margherita"));
hamburgerService.getHamburgerReactive(id).doOnNext(builder::withHamburger);
//hamburger will never be set,
//because Mono returned from getHamburgerReactive() is not subscribed to
return builder.build();
}
教训: 每一个Mono和Flux都需要进行订阅。
在控制器中返回的 响应式类型就是一种隐式订阅。
正如我之前所展示的(在第1阶段),.block()有时用于将反应函数加入到阻塞代码中。
Food getFoodBlocking(int id) {
return foodService.orderFoodReactive(id).block();
}
在Reactor线程中无法调用此函数。这种尝试会导致以下错误:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
.block()只允许在其他线程中使用显式用法(请参阅参考资料.subscribeOn())。
Reactor抛出一个异常并告知我们这个问题是有帮助的。
不幸的是,许多其他方案允许将阻塞代码插入到Reactor线程中,这不会自动检测到。
学到的经验教训:
没有什么能阻止我们将阻塞代码添加到被动流中。
而且,我们不需要使用.block()- 我们可以通过使用可以阻止当前线程的库无意识地引入阻塞。
请考虑以下代码示例。第一个类似于正确的“响应式”延迟。
Mono<Food> getFood(int id) {
return foodService.orderFood(id)
.delayElement(Duration.ofMillis(1000));
}
另一个示例模拟了一个危险的延迟,它阻塞了订户线程。
Mono<Food> getFood(int id) throws InterruptedException {
return foodService
.orderFood(id)
.doOnNext(food -> Thread.sleep(1000));
}
一目了然,这两个版本似乎都有效。
当我们在localhost上运行此应用程序并尝试请求服务时,我们可以看到类似的行为。
“Hello,world!”在延迟1秒后返回。
然而,这种观察极具误导性。
实际上,具有反应式延迟(上一段代码)的版本在重负载下运行良好,另一方面,具有阻塞延迟(下一段代码)的版本重负载下运行糟糕。
所以,大家要重视对应用程序进行性能测试,尤其是考虑外部调用的延迟。
Reactor有许多有用的方法,有助于编写富有表现力和声明性的代码。
但是,其中一些可能有点棘手。
请考虑以下代码:
String valueFromCache = "some non-empty value";
return Mono.justOrEmpty(valueFromCache)
.switchIfEmpty(Mono.just(getValueFromService()));
我们使用类似的代码检查特定值的缓存,然后在缺少值时调用外部服务。
作者的意图似乎很明确:getValueFromService() 仅在缺少缓存值的情况下执行。
但是,此代码每次都会运行,即使是缓存命中也是如此。
赋给.switchIfEmpty()的参数不是lambda,而是Mono.just()直接执行作为参数传递的代码。
显而易见的解决方案是使用Mono.fromSupplier(),并将条件代码作为lambda传递,如下例所示:
String valueFromCache = "some non-empty value";
return Mono.justOrEmpty(valueFromCache)
.switchIfEmpty(Mono.fromSupplier(() -> getValueFromService()));
经验教训: Reactor API有许多不同的方法。
始终考虑一个问题:参数是应该按原样传递,还是用lambda包装。
总结一下,在迁移到WebFlux之后检查我们服务的生产指标。
明显而直接的影响是应用程序使用的线程数量减少。
有趣的是,我们没有将应用程序类型更改为Reactive(我们仍然使用servlet,有关详细信息,请参阅第3阶段),但Undertow工作线程的使用也变小了一个数量级。
低级指标如何受到影响?
我们观察到更少的垃圾收集,并且他们花费的时间更少。
每10分钟的GC耗时
此外,响应时间略有下降,但我们没有预料到这样的效果。
其他指标(如CPU负载,文件描述符使用情况和消耗的总内存)未发生变化。我们的服务也做了很多工作,这与调用无关。将流量迁移到HTTP客户端和控制器周围的响应是至关重要的,但在资源使用方面并不重要。
正如我在开始时所说的那样,**迁移的预期收益是延迟的可扩展性和弹性。我们确信我们已经实现了这一目标。
结论
您是否正在迁移现有的微服务?考虑到文章中涉及的因素,不仅仅是技术因素 - 检查时间和人员使用新解决方案的能力。
始终测试您的应用程序 , 在迁移过程中,覆盖外部耗时调用的集成和性能测试至关重要。
请记住,响应式思维不同于众所周知的阻塞式、命令式思维。
spring默认redis连接库lettuce性能优化,突破性能天花板,获得官方建议方式2倍吞吐量
spring-data-redis性能优化,使用lettuce库连接池模式,比官方推荐方式性能翻倍。
所有命令都返回订阅者可以订阅的Flux
, Mono
或Mono
。
该订阅者对Publisher
发出的任何项目或项目序列做出反应。
此模式有助于并发操作,因为在等待Publisher
发出对象时不需要阻塞。 相反,它以订阅者的形式创建一个哨兵,随时准备在Publisher
以后的任何时间做出适当的反应。
Flux
和Mono
建立发布者的方法有很多。
你已经看过just()
,take()
和collectList()
。 有关可用于创建Flux
和Mono
的更多方法,请参考Project Reactor文档。
Lettuce发布者可用于初始和链接(chaining)操作。
使用Lettuce发布者时,你会注意到非阻塞行为。
这是因为所有的I/O和命令处理都是使用netty EventLoop异步处理的。
连接到Redis非常简单:
RedisClient client = RedisClient.create("redis://localhost");
RedisStringReactiveCommands<String, String> commands = client.connect().reactive();
下一步,从键获取值需要GET操作:
commands.get("key").subscribe(new Consumer<String>() {
public void accept(String value) {
System.out.println(value);
}
});
或者,用Java 8 lambdas编写:
commands
.get("key")
.subscribe(value -> System.out.println(value));
执行是异步处理的,并且在Netty EventLoop线程上完成操作时,可以使用调用线程在处理中进行处理。 由于其解耦性质,可以在完成Publisher
的执行之前保留调用方法。
可以在链接(chaining)的上下文中使用Lettuce发布者来异步加载多个键:
Flux.just("Ben", "Michael", "Mark").
flatMap(key -> commands.get(key)).
subscribe(value -> System.out.println("Got value: " + value));
文章已经太长,这里不做赘述,具体请参考尼恩3高架构笔记
SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,
在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,
仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。
而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
有关 Spring Cloud Gateway 实战, 具体请参考尼恩的 深度文章:
《SpringCloud gateway (史上最全))》
特别说明:
Spring Cloud Gateway 底层使用了高性能的通信框架Netty。
Netty 是高性能中间件的通讯底座, rocketmq 、seata、nacos 、sentinel 、redission 、dubbo 等太多、太多的的大名鼎鼎的中间件,无一例外都是基于netty。
可以毫不夸张的说: netty 是进入大厂、走向高端 的必备技能。
要想深入了解springcloud gateway ,最好是掌握netty 编程。
有关 netty学习 具体请参见机工社出版 、尼恩的畅销书: 《Java高并发核心编程卷 1》
先看ServerWebExchange
的注释:
Contract for an HTTP request-response interaction.
Provides access to the HTTP request and response and also exposes additional server-side processing related properties and features such as request attributes.
翻译一下大概是:
ServerWebExchange是一个HTTP请求-响应交互的契约。
提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。
其实,ServerWebExchange
命名为服务网络交换器,存放着重要的请求-响应属性、请求实例和响应实例等等,有点像Context
的角色。
ServerWebExchange
与过滤器的关系:Spring Cloud Gateway同zuul类似,有“pre”和“post”两种方式的filter。
客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端。
引用Spring Cloud Gateway官网上的一张图:
与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在Spring Cloud Gateway中,filter从作用范围可分为另外两种,
一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;
一种是针对于所有路由的global gateway filer。
现在从作用范围划分的维度来讲解这两种filter。
我们在使用Spring Cloud Gateway
的时候,注意到过滤器(包括GatewayFilter
、GlobalFilter
和过滤器链GatewayFilterChain
)。
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:
Spring Cloud Gateway框架内置的GlobalFilter如下:
上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。
但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。
过滤器都依赖到ServerWebExchange
:
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GatewayFilter extends ShortcutConfigurable {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
public interface GatewayFilterChain {
Mono<Void> filter(ServerWebExchange exchange);
}
这里的设计和Servlet
中的Filter
是相似的,
当前过滤器可以决定是否执行下一个过滤器的逻辑,由GatewayFilterChain#filter()
是否被调用来决定。
而ServerWebExchange
就相当于当前请求和响应的上下文。
ServerWebExchange
实例不单存储了Request
和Response
对象,还提供了一些扩展方法,如果想实现改造请求参数或者响应参数,就必须深入了解ServerWebExchange
。
ServerWebExchange
接口的所有方法:
public interface ServerWebExchange {
// 日志前缀属性的KEY,值为org.springframework.web.server.ServerWebExchange.LOG_ID
// 可以理解为 attributes.set("org.springframework.web.server.ServerWebExchange.LOG_ID","日志前缀的具体值");
// 作用是打印日志的时候会拼接这个KEY对饮的前缀值,默认值为""
String LOG_ID_ATTRIBUTE = ServerWebExchange.class.getName() + ".LOG_ID";
String getLogPrefix();
// 获取ServerHttpRequest对象
ServerHttpRequest getRequest();
// 获取ServerHttpResponse对象
ServerHttpResponse getResponse();
// 返回当前exchange的请求属性,返回结果是一个可变的Map
Map<String, Object> getAttributes();
// 根据KEY获取请求属性
@Nullable
default <T> T getAttribute(String name) {
return (T) getAttributes().get(name);
}
// 根据KEY获取请求属性,做了非空判断
@SuppressWarnings("unchecked")
default <T> T getRequiredAttribute(String name) {
T value = getAttribute(name);
Assert.notNull(value, () -> "Required attribute '" + name + "' is missing");
return value;
}
// 根据KEY获取请求属性,需要提供默认值
@SuppressWarnings("unchecked")
default <T> T getAttributeOrDefault(String name, T defaultValue) {
return (T) getAttributes().getOrDefault(name, defaultValue);
}
// 返回当前请求的网络会话
Mono<WebSession> getSession();
// 返回当前请求的认证用户,如果存在的话
<T extends Principal> Mono<T> getPrincipal();
// 返回请求的表单数据或者一个空的Map,只有Content-Type为application/x-www-form-urlencoded的时候这个方法才会返回一个非空的Map -- 这个一般是表单数据提交用到
Mono<MultiValueMap<String, String>> getFormData();
// 返回multipart请求的part数据或者一个空的Map,只有Content-Type为multipart/form-data的时候这个方法才会返回一个非空的Map -- 这个一般是文件上传用到
Mono<MultiValueMap<String, Part>> getMultipartData();
// 返回Spring的上下文
@Nullable
ApplicationContext getApplicationContext();
// 这几个方法和lastModified属性相关
boolean isNotModified();
boolean checkNotModified(Instant lastModified);
boolean checkNotModified(String etag);
boolean checkNotModified(@Nullable String etag, Instant lastModified);
// URL转换
String transformUrl(String url);
// URL转换映射
void addUrlTransformer(Function<String, String> transformer);
// 注意这个方法,方法名是:改变,这个是修改ServerWebExchange属性的方法,返回的是一个Builder实例,Builder是ServerWebExchange的内部类
default Builder mutate() {
return new DefaultServerWebExchangeBuilder(this);
}
interface Builder {
// 覆盖ServerHttpRequest
Builder request(Consumer<ServerHttpRequest.Builder> requestBuilderConsumer);
Builder request(ServerHttpRequest request);
// 覆盖ServerHttpResponse
Builder response(ServerHttpResponse response);
// 覆盖当前请求的认证用户
Builder principal(Mono<Principal> principalMono);
// 构建新的ServerWebExchange实例
ServerWebExchange build();
}
}
注意到ServerWebExchange#mutate()
方法,ServerWebExchange
实例可以理解为不可变实例,
如果我们想要修改它,需要通过mutate()
方法生成一个新的实例,例如这样:
public class CustomGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 这里可以修改ServerHttpRequest实例
ServerHttpRequest newRequest = ...
ServerHttpResponse response = exchange.getResponse();
// 这里可以修改ServerHttpResponse实例
ServerHttpResponse newResponse = ...
// 构建新的ServerWebExchange实例
ServerWebExchange newExchange = exchange.mutate().request(newRequest).response(newResponse).build();
return chain.filter(newExchange);
}
}
ServerHttpRequest
实例是用于承载请求相关的属性和请求体,
Spring Cloud Gateway
中底层使用Netty
处理网络请求,通过追溯源码,
可以从ReactorHttpHandlerAdapter
中得知ServerWebExchange
实例中持有的ServerHttpRequest
实例的具体实现是ReactorServerHttpRequest
。
之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:
ReactorServerHttpRequest
的父类AbstractServerHttpRequest
中初始化内部属性headers的时候把请求的HTTP头部封装为只读的实例:public AbstractServerHttpRequest(URI uri, @Nullable String contextPath, HttpHeaders headers) {
this.uri = uri;
this.path = RequestPath.parse(uri, contextPath);
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
}
// HttpHeaders类中的readOnlyHttpHeaders方法,
// ReadOnlyHttpHeaders屏蔽了所有修改请求头的方法,直接抛出UnsupportedOperationException
public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
Assert.notNull(headers, "HttpHeaders must not be null");
if (headers instanceof ReadOnlyHttpHeaders) {
return headers;
}
else {
return new ReadOnlyHttpHeaders(headers);
}
}
所以, 不能直接从ServerHttpRequest
实例中直接获取请求头HttpHeaders
实例并且进行修改。
ServerHttpRequest
接口如下:
public interface HttpMessage {
// 获取请求头,目前的实现中返回的是ReadOnlyHttpHeaders实例,只读
HttpHeaders getHeaders();
}
public interface ReactiveHttpInputMessage extends HttpMessage {
// 返回请求体的Flux封装
Flux<DataBuffer> getBody();
}
public interface HttpRequest extends HttpMessage {
// 返回HTTP请求方法,解析为HttpMethod实例
@Nullable
default HttpMethod getMethod() {
return HttpMethod.resolve(getMethodValue());
}
// 返回HTTP请求方法,字符串
String getMethodValue();
// 请求的URI
URI getURI();
}
public interface ServerHttpRequest extends HttpRequest, ReactiveHttpInputMessage {
// 连接的唯一标识或者用于日志处理标识
String getId();
// 获取请求路径,封装为RequestPath对象
RequestPath getPath();
// 返回查询参数,是只读的MultiValueMap实例
MultiValueMap<String, String> getQueryParams();
// 返回Cookie集合,是只读的MultiValueMap实例
MultiValueMap<String, HttpCookie> getCookies();
// 远程服务器地址信息
@Nullable
default InetSocketAddress getRemoteAddress() {
return null;
}
// SSL会话实现的相关信息
@Nullable
default SslInfo getSslInfo() {
return null;
}
// 修改请求的方法,返回一个建造器实例Builder,Builder是内部类
default ServerHttpRequest.Builder mutate() {
return new DefaultServerHttpRequestBuilder(this);
}
interface Builder {
// 覆盖请求方法
Builder method(HttpMethod httpMethod);
// 覆盖请求的URI、请求路径或者上下文,这三者相互有制约关系,具体可以参考API注释
Builder uri(URI uri);
Builder path(String path);
Builder contextPath(String contextPath);
// 覆盖请求头
Builder header(String key, String value);
Builder headers(Consumer<HttpHeaders> headersConsumer);
// 覆盖SslInfo
Builder sslInfo(SslInfo sslInfo);
// 构建一个新的ServerHttpRequest实例
ServerHttpRequest build();
}
}
注意:
ServerHttpRequest
或者说HttpMessage
接口提供的获取请求头方法HttpHeaders getHeaders();
返回结果是一个只读的实例,具体是ReadOnlyHttpHeaders
类型,
如果要修改ServerHttpRequest
实例,那么需要这样做:
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequest newRequest = request.mutate().header("key","value").path("/myPath").build();
ServerHttpResponse
实例是用于承载响应相关的属性和响应体,
Spring Cloud Gateway
中底层使用Netty
处理网络请求,通过追溯源码,可以从ReactorHttpHandlerAdapter
中得知ServerWebExchange
实例中持有的ServerHttpResponse
实例的具体实现是ReactorServerHttpResponse
。
之所以列出这些实例之间的关系,是因为这样比较容易理清一些隐含的问题,例如:
// ReactorServerHttpResponse的父类
public AbstractServerHttpResponse(DataBufferFactory dataBufferFactory, HttpHeaders headers) {
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
Assert.notNull(headers, "HttpHeaders must not be null");
this.dataBufferFactory = dataBufferFactory;
this.headers = headers;
this.cookies = new LinkedMultiValueMap<>();
}
public ReactorServerHttpResponse(HttpServerResponse response, DataBufferFactory bufferFactory) {
super(bufferFactory, new HttpHeaders(new NettyHeadersAdapter(response.responseHeaders())));
Assert.notNull(response, "HttpServerResponse must not be null");
this.response = response;
}
可知ReactorServerHttpResponse
构造函数初始化实例的时候,存放响应Header的是HttpHeaders
实例,也就是响应Header是可以直接修改的。
ServerHttpResponse
接口如下:
public interface HttpMessage {
// 获取响应Header,目前的实现中返回的是HttpHeaders实例,可以直接修改
HttpHeaders getHeaders();
}
public interface ReactiveHttpOutputMessage extends HttpMessage {
// 获取DataBufferFactory实例,用于包装或者生成数据缓冲区DataBuffer实例(创建响应体)
DataBufferFactory bufferFactory();
// 注册一个动作,在HttpOutputMessage提交之前此动作会进行回调
void beforeCommit(Supplier<? extends Mono<Void>> action);
// 判断HttpOutputMessage是否已经提交
boolean isCommitted();
// 写入消息体到HTTP协议层
Mono<Void> writeWith(Publisher<? extends DataBuffer> body);
// 写入消息体到HTTP协议层并且刷新缓冲区
Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body);
// 指明消息处理已经结束,一般在消息处理结束自动调用此方法,多次调用不会产生副作用
Mono<Void> setComplete();
}
public interface ServerHttpResponse extends ReactiveHttpOutputMessage {
// 设置响应状态码
boolean setStatusCode(@Nullable HttpStatus status);
// 获取响应状态码
@Nullable
HttpStatus getStatusCode();
// 获取响应Cookie,封装为MultiValueMap实例,可以修改
MultiValueMap<String, ResponseCookie> getCookies();
// 添加响应Cookie
void addCookie(ResponseCookie cookie);
}
这里可以看到除了响应体比较难修改之外,其他的属性都是可变的。
ServerWebExchangeUtils
里面存放了很多静态公有的字符串KEY值
(这些字符串KEY的实际值是org.springframework.cloud.gateway.support.ServerWebExchangeUtils.
+ 下面任意的静态公有KEY),
这些字符串KEY值一般是用于ServerWebExchange
的属性(Attribute
,见上文的ServerWebExchange#getAttributes()
方法)的KEY,这些属性值都是有特殊的含义,在使用过滤器的时候如果时机适当可以直接取出来使用,下面逐个分析。
PRESERVE_HOST_HEADER_ATTRIBUTE
:是否保存Host属性,值是布尔值类型,写入位置是PreserveHostHeaderGatewayFilterFactory
,使用的位置是NettyRoutingFilter
,作用是如果设置为true,HTTP请求头中的Host属性会写到底层Reactor-Netty的请求Header属性中。CLIENT_RESPONSE_ATTR
:保存底层Reactor-Netty的响应对象,类型是reactor.netty.http.client.HttpClientResponse
。CLIENT_RESPONSE_CONN_ATTR
:保存底层Reactor-Netty的连接对象,类型是reactor.netty.Connection
。URI_TEMPLATE_VARIABLES_ATTRIBUTE
:PathRoutePredicateFactory
解析路径参数完成之后,把解析完成后的占位符KEY-路径Path映射存放在ServerWebExchange
的属性中,KEY就是URI_TEMPLATE_VARIABLES_ATTRIBUTE
。CLIENT_RESPONSE_HEADER_NAMES
:保存底层Reactor-Netty的响应Header的名称集合。GATEWAY_ROUTE_ATTR
:用于存放RoutePredicateHandlerMapping
中匹配出来的具体的路由(org.springframework.cloud.gateway.route.Route
)实例,通过这个路由实例可以得知当前请求会路由到下游哪个服务。GATEWAY_REQUEST_URL_ATTR
:java.net.URI
类型的实例,这个实例代表直接请求或者负载均衡处理之后需要请求到下游服务的真实URI。GATEWAY_ORIGINAL_REQUEST_URL_ATTR
:java.net.URI
类型的实例,需要重写请求URI的时候,保存原始的请求URI。GATEWAY_HANDLER_MAPPER_ATTR
:保存当前使用的HandlerMapping
具体实例的类型简称(一般是字符串"RoutePredicateHandlerMapping")。GATEWAY_SCHEME_PREFIX_ATTR
:确定目标路由URI中如果存在schemeSpecificPart属性,则保存该URI的scheme在此属性中,路由URI会被重新构造,见RouteToRequestUrlFilter
。GATEWAY_PREDICATE_ROUTE_ATTR
:用于存放RoutePredicateHandlerMapping
中匹配出来的具体的路由(org.springframework.cloud.gateway.route.Route
)实例的ID。WEIGHT_ATTR
:实验性功能(此版本还不建议在正式版本使用)存放分组权重相关属性,见WeightCalculatorWebFilter
。ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR
:存放响应Header中的ContentType的值。HYSTRIX_EXECUTION_EXCEPTION_ATTR
:Throwable
的实例,存放的是Hystrix执行异常时候的异常实例,见HystrixGatewayFilterFactory
。GATEWAY_ALREADY_ROUTED_ATTR
:布尔值,用于判断是否已经进行了路由,见NettyRoutingFilter
。GATEWAY_ALREADY_PREFIXED_ATTR
:布尔值,用于判断请求路径是否被添加了前置部分,见PrefixPathGatewayFilterFactory
。ServerWebExchangeUtils
提供的上下文属性用于Spring Cloud Gateway
的ServerWebExchange
组件处理请求和响应的时候,内部一些重要实例或者标识属性的安全传输和使用,使用它们可能存在一定的风险,
因为没有人可以确定在版本升级之后,原有的属性KEY或者VALUE是否会发生改变,如果评估过风险或者规避了风险之后,可以安心使用。
例如我们**在做请求和响应日志(类似Nginx的Access Log)的时候,可以依赖到GATEWAY_ROUTE_ATTR
,因为我们要打印路由的目标信息。**举个简单例子:
@Slf4j
@Component
public class AccessLogFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
HttpMethod method = request.getMethod();
// 获取路由的目标URI
URI targetUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
InetSocketAddress remoteAddress = request.getRemoteAddress();
return chain.filter(exchange.mutate().build()).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("请求路径:{},客户端远程IP地址:{},请求方法:{},目标URI:{},响应码:{}",
path, remoteAddress, method, targetUri, statusCode);
}));
}
}
修改请求体是一个比较常见的需求。
例如我们使用Spring Cloud Gateway
实现网关的时候,要实现一个功能:
把存放在请求头中的JWT解析后,提取里面的用户ID,然后写入到请求体中。
我们简化这个场景,假设我们把userId明文存放在请求头中的accessToken中,请求体是一个JSON结构:
{
"serialNumber": "请求流水号",
"payload" : {
// ... 这里是有效载荷,存放具体的数据
}
}
我们需要提取accessToken,也就是userId插入到请求体JSON中如下:
{
"userId": "用户ID",
"serialNumber": "请求流水号",
"payload" : {
// ... 这里是有效载荷,存放具体的数据
}
}
这里为了简化设计,用全局过滤器GlobalFilter
实现,实际需要结合具体场景考虑:
@Slf4j
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter {
private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
@Autowired
private ObjectMapper objectMapper;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String accessToken = request.getHeaders().getFirst("accessToken");
if (!StringUtils.hasLength(accessToken)) {
throw new IllegalArgumentException("accessToken");
}
// 新建一个ServerHttpRequest装饰器,覆盖需要装饰的方法
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
Flux<DataBuffer> body = super.getBody();
InputStreamHolder holder = new InputStreamHolder();
body.subscribe(buffer -> holder.inputStream = buffer.asInputStream());
if (null != holder.inputStream) {
try {
// 解析JSON的节点
JsonNode jsonNode = objectMapper.readTree(holder.inputStream);
Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式异常");
ObjectNode objectNode = (ObjectNode) jsonNode;
// JSON节点最外层写入新的属性
objectNode.put("userId", accessToken);
DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();
String json = objectNode.toString();
log.info("最终的JSON数据为:{}", json);
dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));
return Flux.just(dataBuffer);
} catch (Exception e) {
throw new IllegalStateException(e);
}
} else {
return super.getBody();
}
}
};
// 使用修改后的ServerHttpRequestDecorator重新生成一个新的ServerWebExchange
return chain.filter(exchange.mutate().request(decorator).build());
}
private class InputStreamHolder {
InputStream inputStream;
}
}
测试一下:
// HTTP
POST /order/json HTTP/1.1
Host: localhost:9090
Content-Type: application/json
accessToken: 10086
Accept: */*
Cache-Control: no-cache
Host: localhost:9090
accept-encoding: gzip, deflate
content-length: 94
Connection: keep-alive
cache-control: no-cache
{
"serialNumber": "请求流水号",
"payload": {
"name": "doge"
}
}
// 日志输出
最终的JSON数据为:{"serialNumber":"请求流水号","payload":{"name":"doge"},"userId":"10086"}
最重要的是用到了ServerHttpRequest
装饰器ServerHttpRequestDecorator
,主要覆盖对应获取请求体数据缓冲区的方法即可,至于怎么处理其他逻辑需要自行考虑,这里只是做一个简单的示范。
一般的代码逻辑如下:
ServerHttpRequest request = exchange.getRequest();
ServerHttpRequestDecorator requestDecorator = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
// 拿到承载原始请求体的Flux
Flux<DataBuffer> body = super.getBody();
// 这里通过自定义方式生成新的承载请求体的Flux
Flux<DataBuffer> newBody = ...
return newBody;
}
}
return chain.filter(exchange.mutate().request(requestDecorator).build());
修改响应体的需求也是比较常见的,具体的做法和修改请求体差不多。
例如我们想要实现下面的功能:第三方服务请求经过网关,原始报文是密文,我们需要在网关实现密文解密,然后把解密后的明文路由到下游服务,下游服务处理成功响应明文,需要在网关把明文加密成密文再返回到第三方服务。
现在简化整个流程,用AES加密算法,统一密码为字符串"throwable",假设请求报文和响应报文明文如下:
// 请求密文
{
"serialNumber": "请求流水号",
"payload" : "加密后的请求消息载荷"
}
// 请求明文(仅仅作为提示)
{
"serialNumber": "请求流水号",
"payload" : "{\"name:\":\"doge\"}"
}
// 响应密文
{
"code": 200,
"message":"ok",
"payload" : "加密后的响应消息载荷"
}
// 响应明文(仅仅作为提示)
{
"code": 200,
"message":"ok",
"payload" : "{\"name:\":\"doge\",\"age\":26}"
}
为了方便一些加解密或者编码解码的实现,需要引入Apache
的commons-codec
类库:
<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
<version>1.12version>
dependency>
这里定义一个全局过滤器专门处理加解密,实际上最好结合真实的场景决定是否适合全局过滤器,这里只是一个示例:
// AES加解密工具类
public enum AesUtils {
// 单例
X;
private static final String PASSWORD = "throwable";
private static final String KEY_ALGORITHM = "AES";
private static final String SECURE_RANDOM_ALGORITHM = "SHA1PRNG";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
public String encrypt(String content) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, provideSecretKey());
return Hex.encodeHexString(cipher.doFinal(content.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
public byte[] decrypt(String content) {
try {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, provideSecretKey());
return cipher.doFinal(Hex.decodeHex(content));
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private SecretKey provideSecretKey() {
try {
KeyGenerator keyGen = KeyGenerator.getInstance(KEY_ALGORITHM);
SecureRandom secureRandom = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHM);
secureRandom.setSeed(PASSWORD.getBytes(StandardCharsets.UTF_8));
keyGen.init(128, secureRandom);
return new SecretKeySpec(keyGen.generateKey().getEncoded(), KEY_ALGORITHM);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
// EncryptionGlobalFilter
@Slf4j
@Component
public class EncryptionGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private ObjectMapper objectMapper;
@Override
public int getOrder() {
return -2;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// 响应体
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
ServerHttpRequestDecorator requestDecorator = processRequest(request, bufferFactory);
ServerHttpResponseDecorator responseDecorator = processResponse(response, bufferFactory);
return chain.filter(exchange.mutate().request(requestDecorator).response(responseDecorator).build());
}
private ServerHttpRequestDecorator processRequest(ServerHttpRequest request, DataBufferFactory bufferFactory) {
Flux<DataBuffer> body = request.getBody();
DataBufferHolder holder = new DataBufferHolder();
body.subscribe(dataBuffer -> {
int len = dataBuffer.readableByteCount();
holder.length = len;
byte[] bytes = new byte[len];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
String text = new String(bytes, StandardCharsets.UTF_8);
JsonNode jsonNode = readNode(text);
JsonNode payload = jsonNode.get("payload");
String payloadText = payload.asText();
byte[] content = AesUtils.X.decrypt(payloadText);
String requestBody = new String(content, StandardCharsets.UTF_8);
log.info("修改请求体payload,修改前:{},修改后:{}", payloadText, requestBody);
rewritePayloadNode(requestBody, jsonNode);
DataBuffer data = bufferFactory.allocateBuffer();
data.write(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
holder.dataBuffer = data;
});
HttpHeaders headers = new HttpHeaders();
headers.putAll(request.getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
return new ServerHttpRequestDecorator(request) {
@Override
public HttpHeaders getHeaders() {
int contentLength = holder.length;
if (contentLength > 0) {
headers.setContentLength(contentLength);
} else {
headers.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return headers;
}
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(holder.dataBuffer);
}
};
}
private ServerHttpResponseDecorator processResponse(ServerHttpResponse response, DataBufferFactory bufferFactory) {
return new ServerHttpResponseDecorator(response) {
@SuppressWarnings("unchecked")
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.map(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
JsonNode jsonNode = readNode(charBuffer.toString());
JsonNode payload = jsonNode.get("payload");
String text = payload.toString();
String content = AesUtils.X.encrypt(text);
log.info("修改响应体payload,修改前:{},修改后:{}", text, content);
setPayloadTextNode(content, jsonNode);
return bufferFactory.wrap(jsonNode.toString().getBytes(StandardCharsets.UTF_8));
}));
}
return super.writeWith(body);
}
};
}
private void rewritePayloadNode(String text, JsonNode root) {
try {
JsonNode node = objectMapper.readTree(text);
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", node);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private void setPayloadTextNode(String text, JsonNode root) {
try {
ObjectNode objectNode = (ObjectNode) root;
objectNode.set("payload", new TextNode(text));
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private JsonNode readNode(String in) {
try {
return objectMapper.readTree(in);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private class DataBufferHolder {
DataBuffer dataBuffer;
int length;
}
}
先准备一份密文:
Map<String, Object> json = new HashMap<>(8);
json.put("serialNumber", "请求流水号");
String content = "{\"name\": \"doge\"}";
json.put("payload", AesUtils.X.encrypt(content));
System.out.println(new ObjectMapper().writeValueAsString(json));
// 输出
{"serialNumber":"请求流水号","payload":"144e3dc734743f5709f1adf857bca473da683246fd612f86ac70edeb5f2d2729"}
模拟请求:
POST /order/json HTTP/1.1
Host: localhost:9090
accessToken: 10086
Content-Type: application/json
User-Agent: PostmanRuntime/7.13.0
Accept: */*
Cache-Control: no-cache
Postman-Token: bda07fc3-ea1a-478c-b4d7-754fe6f37200,634734d9-feed-4fc9-ba20-7618bd986e1c
Host: localhost:9090
cookie: customCookieName=customCookieValue
accept-encoding: gzip, deflate
content-length: 104
Connection: keep-alive
cache-control: no-cache
{
"serialNumber": "请求流水号",
"payload": "FE49xzR0P1cJ8a34V7ykc9poMkb9YS+GrHDt618tJyk="
}
// 响应结果
{
"serialNumber": "请求流水号",
"payload": "oo/K1igg2t/S8EExkBVGWOfI1gAh5pBpZ0wyjNPW6e8=" # <--- 解密后:{"name":"doge","age":26}
}
遇到的问题:
Ordered
接口,返回一个小于-1的order值,这是因为NettyWriteResponseFilter
的order值为-1,我们需要覆盖返回响应体的逻辑,自定义的GlobalFilter
必须比NettyWriteResponseFilter
优先执行。ServerHttpRequest
读取到有效的Body,准确来说出现的现象是NettyRoutingFilter
调用ServerHttpRequest#getBody()
的时候获取到一个空的对象,导致空指针;奇怪的是从第二个请求开始就能正常调用。笔者把Spring Cloud Gateway
的版本降低到Finchley.SR3
,Spring Boot
的版本降低到2.0.8.RELEASE
,问题不再出现,初步确定是Spring Cloud Gateway
版本升级导致的兼容性问题或者是BUG。最重要的是用到了ServerHttpResponse
装饰器ServerHttpResponseDecorator
,主要覆盖写入响应体数据缓冲区的部分,至于怎么处理其他逻辑需要自行考虑,这里只是做一个简单的示范。一般的代码逻辑如下:
ServerHttpResponse response = exchange.getResponse();
ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> flux = (Flux<? extends DataBuffer>) body;
return super.writeWith(flux.map(buffer -> {
// buffer就是原始的响应数据的缓冲区
// 下面处理完毕之后返回新的响应数据的缓冲区即可
return bufferFactory.wrap(...);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(responseDecorator).build());
R2DBC 是一个异步操作数据库的驱动,区别于传统的同步数据库驱动 JDBC,R2DBC 与数据库的各种操作也是异步的,这将大量节省高并发系统的线程数量。
首先,创建一个 User 实体类用于测试,同时在 MySQL 中创建相应的数据库以及表结构
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("webflux_user")
public class User {
@Id
private int id;
private String username;
private String password;
}
编写数据仓库层,使用 Spring-data 封装好的简单 CRUD 接口(用法类似 JPA)
package com.crazymaker.springcloud.reactive.user.info.dao.impl;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface UserRepository extends ReactiveCrudRepository<User, Integer> {
}
此时就可以调用封装好的 CRUD 方法进行简单的增删改查操作了。
在 Webflux 框架中,我们可以使用 SpringMVC 中 Controller + Service 的模式进行开发,也可以使用 Webflux 中 route + handler 的模式进行开发。
编写 Service 调用 UserRepository
package com.crazymaker.springcloud.reactive.user.info.service.impl;
import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.reactive.user.info.dao.impl.UserRepository;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Mono<User> addUser(User user) {
return userRepository.save(user);
}
public Mono<RestOut<Void>> delUser(long id) {
return userRepository.findById(id)
.flatMap(user -> userRepository.delete(user).then(Mono.just( RestOut.<Void>succeed("delete ok "))))
.defaultIfEmpty(RestOut.<Void>succeed("没有找到数据 "));
}
public Mono<RestOut<User>> updateUser(User user) {
return userRepository.findById(user.getUserId())
.flatMap(user0 -> userRepository.save(user))
.map(user0 -> RestOut.success(user))
.defaultIfEmpty(RestOut.<User>succeed("没有找到数据 "));
}
public Flux<User> getAllUser() {
return userRepository.findAll();
}
}
编写 Controller 进行测试
package com.crazymaker.springcloud.reactive.user.info.controller;
import com.crazymaker.springcloud.common.result.RestOut;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import com.crazymaker.springcloud.reactive.user.info.service.impl.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.RestOut;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public Mono<User> addUser(@RequestBody User user) {
return userService.addUser(user);
}
@DeleteMapping("/{id}")
public Mono<RestOut<Void>> delUser(@PathVariable int id) {
return userService.delUser(id);
}
@PutMapping
public Mono<RestOut<User>> updateUser(@RequestBody User user) {
return userService.updateUser(user);
}
@GetMapping
public Flux<User> getAllUser() {
return userService.getAllUser();
}
}
handler 就相当于定义很多处理器,其中不同的方法负责处理不同路由的请求,其对应的是传统的 Service 层
package com.crazymaker.springcloud.reactive.user.info.handler;
import com.crazymaker.springcloud.reactive.user.info.dao.impl.UserRepository;
import com.crazymaker.springcloud.reactive.user.info.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class UserHandler {
@Autowired
private UserRepository userRepository;
public Mono<ServerResponse> addUser(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);
}
public Mono<ServerResponse> delUser(ServerRequest request) {
return userRepository.findById(Long.parseLong(request.pathVariable("id")))
.flatMap(user -> userRepository.delete(user).then(ServerResponse.ok().build()))
.switchIfEmpty(ServerResponse.notFound().build());
}
public Mono<ServerResponse> updateUser(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.saveAll(request.bodyToMono(User.class)), User.class);
}
public Mono<ServerResponse> getAllUser(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(userRepository.findAll(), User.class);
}
public Mono<ServerResponse> getAllUserStream(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.TEXT_EVENT_STREAM)
.body(userRepository.findAll(), User.class);
}
}
route 就是路由配置,其规定路由的分发规则,将不同的请求路由分发给相应的 handler 进行业务逻辑的处理,其对应的就是传统的 Controller 层
@Configuration
public class RouteConfig {
@Bean
RouterFunction<ServerResponse> userRoute(UserHandler userHandler) {
return RouterFunctions.nest(
RequestPredicates.path("/userRoute"),
RouterFunctions.route(RequestPredicates.POST(""), userHandler::addUser)
.andRoute(RequestPredicates.DELETE("/{id}"), userHandler::delUser)
.andRoute(RequestPredicates.PUT(""), userHandler::updateUser)
.andRoute(RequestPredicates.GET(""), userHandler::getAllUser)
.andRoute(RequestPredicates.GET("/stream"), userHandler::getAllUserStream)
);
}
}
https://blog.csdn.net/wpc2018/article/details/122634049
https://www.jianshu.com/p/7d80b94068b3
https://blog.csdn.net/yhj_911/article/details/119540000
http://bjqianye.cn/detail/6845.html
https://blog.csdn.net/hao134838/article/details/110824092
https://blog.csdn.net/hao134838/article/details/110824092
https://blog.csdn.net/weixin_34096182/article/details/91436704
https://blog.csdn.net/fly910905/article/details/121682625
https://gaocher.github.io/2020/01/05/mono-create/
《全链路异步,让你的 SpringCloud 性能优化10倍+》
《Linux命令大全:2W多字,一次实现Linux自由》
《网易二面:CPU狂飙900%,该怎么处理?》
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
《场景题:假设10W人突访,你的系统如何做到不 雪崩?》
《2个大厂 100亿级 超大流量 红包 架构方案》
《Nginx面试题(史上最全 + 持续更新)》
《K8S面试题(史上最全 + 持续更新)》
《操作系统面试题(史上最全、持续更新)》
《Docker面试题(史上最全 + 持续更新)》
《Springcloud gateway 底层原理、核心实战 (史上最全)》
《Flux、Mono、Reactor 实战(史上最全)》
《sentinel (史上最全)》
《Nacos (史上最全)》
《TCP协议详解 (史上最全)》
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《clickhouse 超底层原理 + 高可用实操 (史上最全)》
《nacos高可用(图解+秒懂+史上最全)》
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《环形队列、 条带环形队列 Striped-RingBuffer (史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
《单例模式(史上最全)》
《红黑树( 图解 + 秒懂 + 史上最全)》
《分布式事务 (秒懂)》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《缓存之王:Caffeine 的使用(史上最全)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》
《Docker原理(图解+秒懂+史上最全)》
《Redis分布式锁(图解 - 秒懂 - 史上最全)》
《Zookeeper 分布式锁 - 图解 - 秒懂》
《Zookeeper Curator 事件监听 - 10分钟看懂》
《Netty 粘包 拆包 | 史上最全解读》
《Netty 100万级高并发服务器配置》
《Springcloud 高并发 配置 (一文全懂)》