網關Spring Cloud Gateway科普

點擊上方“朱小廝的博客”,選擇“設爲星標”

後臺回覆”加羣“獲取公衆號專屬羣聊入口

歡迎跳轉到本文的原文鏈接:https://honeypps.com/backend/introduction-of-spring-cloud-gateway/

Spring Cloud Gateway是在Spring生態系統之上構建的API網關服務,它旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能, 例如:熔斷、限流、重試等。

Spring Cloud Gateway 具有如下特性:

  • 基於Spring Framework 5、Project Reactor 和 Spring Boot 2.0 進行構建;

  • 動態路由:能夠匹配任何請求屬性;

  • 可以對路由指定 Predicate(斷言)和 Filter(過濾器);

  • 集成Hystrix的斷路器功能;

  • 集成 Spring Cloud 服務發現功能;

  • 易於編寫的 Predicate(斷言)和 Filter(過濾器);

  • 請求限流功能;

  • 支持路徑重寫。

Spring Cloud Gateway 作爲 Spring Cloud 生態系統中的網關,目標是替代 Zuul,在Spring Cloud 2.0以上版本中,沒有對新版本的Zuul 2.0以上最新高性能版本進行集成,仍然還是使用的 Zuul 2.0之前的非Reactor模式的老版本。(有一個版本的說法是 Zuul2.x 的連續跳票和 Zuul1.x 的性能並不是很理想,從而催生了Spring Cloud Gateway。)而爲了提升網關的性能,SpringCloud Gateway是基於WebFlux框架實現的,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty。

網上很多地方都說 Zuul 是阻塞的,Spring Cloud Gateway 是非阻塞的,這麼說並不嚴謹的,準確的講 Zuul1.x 是阻塞的,而在2.x的版本中,Zuul也是基於Netty,也是非阻塞的,如果一定要說性能,其實這個真沒多大差距。Zuul的有關信息可以參考《網關Zuul科普》。

Spring Cloud Gateway有以下幾個重要的概念,後面的內容也主要圍繞這幾個概念展開。

  • Route(路由):路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言爲true則匹配該路由;

  • Predicate(斷言):指的是Java 8 的 Function Predicate。輸入類型是Spring框架中的ServerWebExchange。這使開發人員可以匹配HTTP請求中的所有內容,例如請求頭或請求參數。如果請求與斷言相匹配,則進行路由;

  • Filter(過濾器):指的是Spring框架中GatewayFilter的實例,使用過濾器,可以在請求被路由前後對請求進行修改。

快速入門

定義2個服務:hello-server和user-server,他們分別都註冊到euraka服務上,示例如下:

在未經過網關時,我們可以通過以下2個接口來分別訪問hello-server和user-server:

http://localhost:8081/hello
http://localhost:8082/user

現在我們來定義一個Spring Cloud Gateway服務,相關的Maven依賴如下:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

application.yml文件中添加如下配置:

server:
  port: 9091
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: path_route_1
          uri: http://localhost:8082/user
          predicates:
            - Path=/user

各字段含義如下:

  • id: 我們自定義的路由ID,唯一。

  • uri:目標服務地址

  • predicates:路由條件,Predicate接受一個輸入參數,返回一個布爾值結果。該接口中包含多種默認方法來將Predicate組合成其他複雜的邏輯(比如:與、或、非)

這段配置的意思是:配置了一個id爲 path_route_1的URI代理規則,當訪問地址 http://localhost:9091/user時,會路由到地址http://localhost:8082/user

Spring Cloud Gateway的路由配置和Zuul的路由配置有較大的區別,玩過Zuul的注意一下兩者的區分。

啓動類如下,平平無奇:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

現在我們啓動Spring Cloud Gateway服務,然後請求http://localhost:9091/user:

hidden:~ hidden$ curl -i localhost:9091/user
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 11
Date: Thu, 02 Apr 2020 09:28:58 GMT

User Info!

由於沒有配置過hello-server的服務,所以還不能通過網關訪問它:

hidden:~ hidden$ curl -i localhost:9091/hello
HTTP/1.1 404 Not Found
Content-Type: application/json
Content-Length: 133

{"timestamp":"2020-04-02T09:30:09.840+0000","path":"/hello","status":404,"error":"Not Found","message":null,"requestId":"8b0a00e1-4"}

除了使用yml配置文件的方式,還可以通過代碼(Java Bean)來實現路由配置。我們在啓動類中添加方法 customRouteLocator() 來定製轉發規則,詳情如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
        return builder.routes()
                .route("path_route_2", r -> r.path("/hello")
                        .uri("http://localhost:8081/hello"))
                .build();
    }
}

重啓服務,再次訪問hello-server的接口,可以看到訪問成功:

hidden:~ hidden$ curl -i localhost:9091/hello
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
Content-Length: 19
Date: Thu, 02 Apr 2020 09:53:25 GMT

Hello!

路由規則

Spring Cloud Gateway將路由匹配作爲Spring WebFlux HandlerMapping基礎架構的一部分。Spring Cloud Gateway 包括許多內置的 Predicate 工廠,這些 Predicate 工廠通過不同的 HTTP 請求參數來匹配,多個 Predicate 工廠可以組合使用。

Predicate 來源於 Java 8,是 Java 8 中引入的一個函數,Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他複雜的邏輯(比如:與,或,非)。可以用於接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。

在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實現了各種路由匹配規則,有通過 Header、請求參數等不同的條件來進行作爲條件匹配到對應的路由。網上有一張圖總結了 Spring Cloud 內置的幾種 Predicate 的實現。

說白了 Predicate 就是爲了實現一組匹配規則,方便讓請求過來找到對應的 Route 進行處理,接下來我們接下 Spring Cloud GateWay 內置幾種 Predicate 的使用。

Cookie

帶有指定Cookie的請求會匹配改路由。將application.yml中的spring.cloud.gateway.routes配置修改爲如下內容:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: http://localhost:8082/user
          predicates:
            - Cookie=username,pipisi

此時使用curl工具發送帶有cookie爲 username=pipisi 的請求就可以匹配改路由:

curl -i localhost:9091/user --cookie "username=pipisi"

Query

帶指定查詢參數的請求可以匹配該路由。將application.yml中的spring.cloud.gateway.routes配置修改爲如下內容:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: http://localhost:8082/user
          predicates:
            - Query=username

此時使用curl工具發送帶有username=pipisi 查詢參數的請求就可以匹配改路由:

curl -i localhost:9091/user?username=pipisi

其餘的還有(參考上圖):通過Header匹配(將predicates中的 -Query 換成 -Header,以下類同)、通過Host匹配(-Host)、通過請求方法匹配(-Method)、通過IP地址匹配(-RemoteAddr)、通過請求時間(-After、-Before、-Between)、通過權重(-Weight)以及最開始在入門示例中就提及的通過請求路徑匹配(-Path)。還可以進行組合使用,比如:

predicates:
  - Query=username
  - Cookie=username,pipisi
  - Path=/user

各種 Predicates 同時存在於同一個路由時,請求必須同時滿足所有的條件才被這個路由匹配。

過濾器

網關經常需要對路由請求進行過濾,執行一些操作,如鑑權之後構造頭部之類的。過濾的種類很多,如增加請求頭、增加請求參數 、增加響應頭以及斷路器等等,這就用到了Spring Cloud Gateway 的 過濾器(Filter)。

當我們有很多服務時,比如前面所提及的user-server和hello-server,客戶端請求各個服務的API接口時,每個服務都要做相同的事情,比如鑑權、限流、日誌等,如下圖(上半部)。

對於這樣重複的工作,可以在微服務的上一層加一個全局的權限控制、限流、日誌的API網關服務,然後將請求轉發到具體的業務服務層。這個API網關服務就是起到一個服務邊界的作用,外界的請求訪問系統,必須先通過網關層,如上圖(下半部)。

Spring Cloud Gateway同 Zuul 類似,也有 prepost 兩種方式的過濾器:pre 過濾器在請求被路由之前調用,我們可利用這種過濾器實現身份驗證、在集羣中選擇請求的微服務、記錄調試信息等;post 過濾器在路由到微服務以後執行,這種過濾器可用來爲響應添加標準的 HTTP Header、收集統計信息和指標、將響應從微服務發送給客戶端等。

Spring Cloud Gateway的 Filter生命週期如下圖所示。

與  Zuul  不同的是,Filter除了分爲prepost 兩種方式的 Filter 外,在Spring Cloud Gateway中,Filter 根據作用範圍可分爲另外兩種,一種是針對於單個路由的 GatewayFilter,它在配置文件中的寫法同 Predicate類似;另外一種是針對於所有路由的 GlobalFilter。

過濾器允許以某種方式修改傳入的HTTP Request 或傳出的HTTP Response,它可以限定作用在某些特定請求路徑上。Spring Cloud Gateway包含了30多個內置的GatewayFilter工廠,比如AddRequestHeaderGatewayFilterFactory(添加請求頭的過濾器工廠)、AddRequestParameterGatewayFilterFactory(添加請求參數的過濾器工廠)、AddResponseHeaderGatewayFilterFactory(添加響應頭的過濾器工廠)、HystrixGatewayFilterFactory(Hystrix熔斷過濾器工廠)、RequestRateLimiterGatewayFilterFactory(請求限流的過濾器工廠)等(全部的GatewayFilter工廠可以查看jar包中org.springframework.cloud.gateway.filter.factory目錄,除了這些,你也可以自定義自己的過濾器)。

GatewayFilter工廠與前面介紹的Predicate工廠類似,都是在配置文件 application.yml 中配置,遵循了約定大於配置的思想,只需要在配置文件配置GatewayFilter Factory的名稱,而不需要寫全部的類名,比如 AddRequestHeaderGatewayFilterFactory 只需要在配置文件中寫AddRequestHeader,而不是全部類名。官方文檔都給出了這些過濾器工廠詳細的使用案例,下面挑選1個案例以作演示。

案例:AddRequestHeader

application.yml配置如下:

server:
  port: 9091
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: http://localhost:8082/user
          filters:
            - AddRequestHeader=X-Request-Foo, Bar
          predicates:
            - Path=/user

過濾器工廠會在匹配的請求頭加上一對請求頭“x-request-foo=Bar”。可以在下游的user-server服務中使用@RequestHeader來查看所接收請求的請求頭信息。

結合註冊中心Eureka使用

Zuul 作爲網關結合註冊中心進行使用時,默認情況下 Zuul 會根據註冊中心註冊的服務列表,以服務名爲路徑創建動態路由,Spring Cloud Gateway同樣也實現了該功能。下面我們演示下 Spring Cloud Gateway結合註冊中心如何使用默認的動態路由。

首先添加Maven依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

其次修改application.yml配置文件:

server:
  port: 9091
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

重啓服務之後,我們就可以像 Zuul 那樣採用下面的方式訪問接口了:

http://localhost:9091/hello-server/hello
http://localhost:9091/user-server/user

工作原理

Spring Cloud Gateway 的核心處理流程如上圖,核心邏輯就是路由轉發,執行過濾器鏈。Gateway的客戶端迴向Spring Cloud Gateway發起請求,然後在 Gateway Handler Mapping 中找到與請求相匹配的路由,將其發送到 Gateway Web Handler。Handler 再通過指定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯,然後返回。在Filter鏈中,通過虛線分割Filter的原因是:過濾器可以在轉發請求之前處理或者接收到被代理服務的返回結果之後處理。所有的Pre類型的Filter執行完畢之後,纔會轉發請求到被代理的服務處理。被代理的服務把所有請求完畢之後,纔會執行Post類型的過濾器。

References

  1. https://www.cnblogs.com/crazymakercircle/p/11704077.html

  2. http://www.imooc.com/article/297943

  3. https://www.cnblogs.com/bjlhx/p/9785926.html

  4. https://www.jianshu.com/p/17bbc8e10545

歡迎跳轉到本文的原文鏈接:https://honeypps.com/backend/introduction-of-spring-cloud-gateway/

想知道更多?描下面的二維碼關注我

後臺回覆”加羣“獲取公衆號專屬羣聊入口

【精彩推薦】

>>> 字節跳動社招內推入口 <<<

>>> 字節跳動校招內推入口 <<<

朕已閱 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章