spring cloud gateway的构建基础

306 阅读10分钟

        上一篇比较详细介绍了API网关技术形态和发展趋势,在聊到目前微服务网关的可选方案时介绍了各种比较成熟的方案,接下来会分几篇来对其中一个基于JAVA技术栈的成熟方案 -- Spring Cloud Gateway(下文简称scg吧), 做详细的介绍。

        作为开篇,本来应该来一个hello world级别的demo,神雕确实之前也是从先玩官方例子开始的。但是demo还是自己动手实操一下比较好,官方例子里有常规的路由、一个简单的断路器、以及熔断时候fallback。此处放个链接可以照着步骤玩一下:

spring.io/guides/gs/g…

        想基于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