上一篇比较详细介绍了API网关技术形态和发展趋势,在聊到目前微服务网关的可选方案时介绍了各种比较成熟的方案,接下来会分几篇来对其中一个基于JAVA技术栈的成熟方案 -- Spring Cloud Gateway(下文简称scg吧), 做详细的介绍。
作为开篇,本来应该来一个hello world级别的demo,神雕确实之前也是从先玩官方例子开始的。但是demo还是自己动手实操一下比较好,官方例子里有常规的路由、一个简单的断路器、以及熔断时候fallback。此处放个链接可以照着步骤玩一下:
想基于scg打造一个生产级别的网关,除了对网关以及需求有较多的了解之外,对scg本身也必须有代码级别的深入理解。如果说在通读官方文档时对 “scg构建在springboot2.x、webflux、project reactor之上”的描述表示无感的话,那么当你看到沿着demo代码看到到处都是flux和mono的时候,一定很想搞明白“what happened"。而当你想弄明白what happened的时候,很快会发现按照spring mvc或者说severlet的思路不work了,里面充满了大量的webflux和project reactor的代码。
下面以上图描述的scg工作流程展示几个代码例子,不必细看代码,只是隔着屏幕感受一下scg基于webflux和reactor的代码风格。比如,scg处理流程的第一个组件,找到RoutePredicateHandlerMapping的获取web handler的核心代码是这样的:
而当你找到Gateway Web Handler (FilteringWebHandler)会发现核心处理代码是这样:
再顺着处理流程找一个最简单filter(ForwardRoutingFilter)看看:
因此,本篇只想作为scg的一个前菜,试图通过简单的介绍阐述清楚这些看似简单的代码背后的逻辑。那么我们再来重新解读一下官方文档的文字描述:
一. WebFlux
1)功能模块:
看下图:右侧是spring 5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。
-
Router Functions: 对标@Controller,@RequestMapping等标准的Spring MVC注解,提供一套函数式风格的API,用于创建Router,Handler和Filter。
-
WebFlux: 核心组件,协调上下游各个组件提供响应式编程支持。
-
Reactive Streams: 一种支持背压(Backpressure)的异步数据流处理标准,主流实现有RxJava和Reactor,Spring WebFlux默认集成的是Reactor。
2)编程模型
WebFlux包含对响应式 HTTP 和 WebSocket 客户端的支持,以及对 REST 、HTML 和 WebSocket 交互等程序 的支持。一般来说,Spring MVC 用于 同步处理,Spring Webflux 用于 异步处理。
3)使用场景:
异步编程模型并不会对单个处理加速,而是在相同资源情况下能够处理更多的请求,这是由其只需要少量的线程做类似回调响应的模式决定的。公认的在Io密集型场景下,webflux这种模式比较有优势。另外一个场景是选择了基于webflux为底层框架的方案,比如scg。但无论处于何种原因,一旦选择了webflux,从控制层到数据层都得使用reactive api,否则底层一个同步的api可能耗尽异步线程给产线带来大麻烦。
4)简单演示:
第一个webflux简单例子,的基于端点定义RouterFunction的hello
a. 添加依赖
implementation("org.springframework.boot:spring-boot-starter-webflux")
b.添加代码
c.运行curl http://localhost:8080/helloflux即可看到结果
d.可以拦截请求做额外的处理,比如添加一个filter
@Componentpublic class RouterFilter
implements HandlerFilterFunction<ServerResponse, ServerResponse> {
@Override
public Mono<ServerResponse> filter(ServerRequest request,
HandlerFunction<ServerResponse> next) {
return ServerResponse.status(HttpStatus.UNAUTHORIZED)
.body(Mono.just("intercepted"),String.class);
}
}
并添加到RouterFunction:
return RouterFunctions.route(GET("/helloflux"), this::hello).filter(routerFilter);
之前的请求就被拦截了:
$ curl http://localhost:8080/helloflux
intercepted
再来看第二个例子,基于注解的webflux
a.代码更简单:
b.运行他会每一秒钟返回给前端一个消息:
二. Project Reactor
上面两个flux的例子不仅简单,而且有一个共同的特征,其返回值都是Mono或者Flux,这就不得不来介绍Project Reactor了。
1. Reactive System
先来看Reactive Manifesto(响应式宣言):
We want systems that are
Responsive, Resilient, Elastic and Message Driven.
We call these Reactive Systems.
-- https://www.reactivemanifesto.org/
四个关键词:
Responsive(可响应的): 系统尽可能做到在任何时候都能及时响应,响应性意味着可以快速发现并有效处理问题。响应系统专注于提供快速一致的响应时间,建立可靠的上限,以便提供一致的服务质量。这种一致的行为反过来简化了错误处理,建立了最终用户的信心,并鼓励进一步的交互。
Resilient(可恢复的):要求系统即使出错了,也能保持可响应性
Elastic(可伸缩的): 要求系统在各种负载下都能保持可响应性。
Message Driven(消息驱动): 依靠异步消息传递在组件之间建立边界,以确保松散耦合、隔离和位置透明。这也是实现背压式(Back-Pressure),管理负载的一个手段。
2. Project Rector
Project Rector是reactive system的一个实现,是一个完全无阻塞包括背压式支持的基础,也是spring生态中响应式技术栈的基础,已经在spring webflux/spring data/spring cloud gw等项目中使用并成为一个特色。先看一个官方的例子感觉一下:
userService.getFavorites(userId) ➊
.flatMap(favoriteService::getDetails) ➋
.switchIfEmpty(suggestionService.getSuggestions()) ➌
.take(5) ➍
.publishOn(UiUtils.uiThreadScheduler()) ➎
.subscribe(uiList::show, UiUtils::errorPopup); ➏
➊ 根据userId获得喜欢的东西,一般是Flux(一个 Publisher)
➋ 使用 flatMap 操作把Favorite映射为Detail,返回Flux(一个新的Publisher)
➌ 使用 switchIfEmpty 操作,在没有喜欢数据的情况下,采用系统推荐的方式获得
➍ 取前五个
➎ 在 uiThread 上进行发布
➏ 订阅给两个subscriber,一个uiList::show,另一个UiUtils::errorPopup
2.1 两个基本概念
Flux 和 Mono 是 Reactor 中的俩基本概念,也是接下来要介绍的重要角色中的publisher。Flux 表示的是包含 0 到 N 个元素的异步序列。在该序列中可包含三种不同类型的消息通知:正常的包含元素的消息、序列结束的消息和序列出错的消息。Mono 表示的是包含 0 或者 1 个元素的异步序列。该序列中同样可以包含与 Flux 相同的三种类型的消息通知。Flux 和 Mono 之间可以进行转换。对一个 Flux 序列进行计数操作,得到的结果是一个 Mono对象。把两个 Mono 序列合并在一起,得到的是一个 Flux 对象。大多数时候,我们写代码都是从Flux/Mono开始,作为Publisher,这两个对象都有声明(自我创建)的静态方法。
2.2 三个重要角色
(1) Pubilsher
package org.reactivestreams;
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
发布者或者说数据提供者,它可以被Subscriber订阅,看上面接口定义也可看出来.
(2) Subscriber
public interface Subscriber<T> {
public void onSubscribe(Subscription s); ➊
public void onNext(T t); ➋
public void onError(Throwable t);
public void onComplete(); ➍
}
订阅者,订阅发布者的数据,并且在订阅的时候会触发onSubscribe方法,进而其他方法也会在不同阶段被触发。
(3) Subscription
public interface Subscription {
public void request(long n); ➊
public void cancel(); ➋
}
订阅,作用桥接publisher和subscriber。就是因为它的存在,使得subscriber可以反馈给publisher,这也是背压-back pressure的实现方式。当subscriber处理速度比较慢的时候,它可以根据自己的速度来request消息或者cancel。
整个工作模式可以简单描述为:Publisher接收Subscriber来订阅(产生一个Subscription),Subscirber通过Subscription对象来向Publisher请求数据,Publisher开始发送数据并逐个触发Subscriber的onNext方法,当出错时并调用onError,当完成最后一个数据发送之后触发Subscriber的cancel方法。
**2.3 五个阶段
**
介绍完上面三个角色,和两个重要概念Flux/Mono(其实都是Pubisher角色),我们就可以来重点理解一下五个阶段了。个人感觉比较烧脑的,所以结合三行简单的代码:
Flux.just("walse","wilson")
.map( s -> s.concat("wu"))
.filter(s -> s.length() > 5)
.subscribe(System.out::println,System.err::println);
1)声明阶段
声明阶段其实是不断的创建Publisher,对应的对象是Flux或者Mono。创建的方法有很多,包括generate,create等等,也包括map、filter等操作。这里声明阶段我们翻译(啰嗦)下:
Flux<String> rawPublisher = Flux.just("walse","wilson");
Flux<String> mapPublisher = rawPublisher.map(s -> s.concat("wu"));
Flux<String> filterPublisher = mapPublisher.filter(
s -> s.length() > 5);
第一行其实是创建一个Flux Array
//reactor.core.publisher.Flux#fromArray
public static <T> Flux<T> fromArray(T[] array) {
// 检查略
return onAssembly(new FluxArray<>(array));
}
第二行把Flux中string元素映射成一个新的string(拼接wu),然后返回一个新的Flux:
public final <V> Flux<V> map(Function<? super T, ? extends V> mapper) {
if (this instanceof Fuseable) {
return onAssembly(new FluxMapFuseable<>(this, mapper));
}
return onAssembly(new FluxMap<>(this, mapper));
}
//看代码发现其实只是创建了一个封装了mapper对象新的FluxMapFuseable
第二行把Flux中string元素进行filter,用过stream api的都能猜测得到应该是会使用一个Predicate进行apply,看下面代码显然猜对了,最后返回一个新的Flux
public final Flux<T> filter(Predicate<? super T> p) {
if (this instanceof Fuseable) {
return onAssembly(new FluxFilterFuseable<>(this, p));
}
return onAssembly(new FluxFilter<>(this, p));
}
所以,上面声明阶段其实创建了三个Flux对象,按角色分的话都是属于publisher角色,用下面对象图描述如下
-
第三个Flux对象filterPublisher,即我们最终得到一个FluxFilterFuseable对象,他持有一个FluxMapFuseable对象,同时封装了一个filter对象在执行的时会去触发apply方法。
-
第二个Flux对象mapPublisher,即FluxMapFuseable对象,他持有一个FluxArray对象,同时封装一个map对象在执行的时候会触发map方法进行结果映射。
-
第一个FluxArray对象rawPublisher,他只是封装了一个array: {"walse", "wilson"}
得出一个结论:声明阶段啥逻辑也没执行,都是创建对象而已.
2)订阅阶段
订阅阶段虽然只是我们对最终得到的filterPublisher对象调用subscribe方法,但却是所有执行逻辑的开始,这就是“nothing happens until you subscribe”,更加确切的说是在所有subscriber都创建完毕之后,开始对源头的subscriber进行onSubscribe触发。在看代码之前,先展示一下示意图:
在subscribe时先从filterPublisher出发,创建一个三个subscriber,最终我们有了上图蓝色框中的subscriber对象,创建顺序如上图1/2/3,对照代码:
# filterPublisher.subscribe(System.out::println,System.err::println)
//in class Flux
public final Disposable subscribe(Consumer<? super T> consumer) {
Objects.requireNonNull(consumer, "consumer");
return subscribe(consumer, null, null, null);
}
public final Disposable subscribe(
@Nullable Consumer<? super T> consumer,
@Nullable Consumer<? super Throwable> errorConsumer,
@Nullable Runnable completeConsumer,
@Nullable Context initialContext)
//注意:这里创建了一个LambdaSubscriber对象去做订阅
return subscribeWith(new LambdaSubscriber<>(consumer, errorConsumer,
completeConsumer,
null,
initialContext));
}
public final <E extends Subscriber<? super T>> E subscribeWith(E subscriber) {
subscribe(subscriber);
return subscriber;
}
//最终的执行逻辑:
@Override
@SuppressWarnings("unchecked")
public final void subscribe(Subscriber<? super T> actual) {
CorePublisher publisher = Operators.onLastAssembly(this);
CoreSubscriber subscriber = Operators.toCoreSubscriber(actual);
try {
if (publisher instanceof OptimizableOperator) {
OptimizableOperator operator = (OptimizableOperator) publisher;
while (true) {
subscriber = operator.subscribeOrReturn(subscriber);
if (subscriber == null) {
return;
}
OptimizableOperator newSource = operator.nextOptimizableSource();
if (newSource == null) {
publisher = operator.source();
break;
}
operator = newSource;
}
}
//这行代码会真正发onSubscribe
publisher.subscribe(subscriber);
}
catch (Throwable e) {
Operators.reportThrowInSubscribe(subscriber, e);
return;
}
}
看上面最后一个方法执行逻辑是一个循环,从先执行filterPublisher的subscribeOrReturn,然后取其source(为mapPublisher)再执行subscribeOrReturn,这两个方法分别都是创建各自的subscribe对象而已:
//FluxFilterFuseable
@Override
public CoreSubscriber<? super T> subscribeOrReturn(CoreSubscriber<? super T> actual) {
if (actual instanceof ConditionalSubscriber) {
return new FilterFuseableConditionalSubscriber<>((ConditionalSubscriber<? super T>) actual,
predicate);
}
return new FilterFuseableSubscriber<>(actual, predicate);
}
//FluxMapFuseable
@Override
@SuppressWarnings("unchecked")
public CoreSubscriber<? super T> subscribeOrReturn(CoreSubscriber<? super R> actual) {
if (actual instanceof ConditionalSubscriber) {
ConditionalSubscriber<? super R> cs = (ConditionalSubscriber<? super R>) actual;
return new MapFuseableConditionalSubscriber<>(cs, mapper);
}
return new MapFuseableSubscriber<>(actual, mapper);
}
3)onSubscribe阶段
这阶段是在所有subscriber创建结束之后开始执行源FluxArray的subscibe,如下debug图,循环体是创建所有publisher的subscriber,跳出循环体之后执行:
看FluxArray.subscribe触发onSubscribe
于是依次调用onSubscribe,最后调用到LambdaSubscriber.onSubscribe方法,于是我们的图又得到了丰满,见红色数字顺序:
4)request阶段
如下图,在LambdaScriber的onSubscribe方法中开始request,其中s是FilterFuseableSubscriber,从debug显示或者上面图中追溯也能发现。
request也是一条链路调用,最后会执行到源FluxArray的request:
@Override
public void request(long n) {
if (Operators.validate(n)) {
if (Operators.addCap(REQUESTED, this, n) == 0) {
if (n == Long.MAX_VALUE) {
fastPath();
}
else {
slowPath(n);
}
}
}
}
5)调用阶段
当进入到FluxArray从request进入到fastPath方法,开始了最终的调用链路:
遍历所有元素挨个onNext:
->在FluxMapFuseable的onNext中apply进行映射:
然后在FluxFilterFuseable中onNext执行predicate逻辑:
最后(如果上面predicate为true)执行到LambdaSubscriber的onNext进行触发真正订阅的最终执行者进行消费:
到这里,代码很明了了吧,正常逻辑执行consumer,异常逻辑会依次cancel然后onError中执行异常errorConsumer