springcloud(十五):Spring Cloud 終於按捺不住推出了自己的服務閘道器 Gateway

純潔的微笑發表於2018-12-19

Spring 官方最終還是按捺不住推出了自己的閘道器元件:Spring Cloud Gateway ,相比之前我們使用的 Zuul(1.x) 它有哪些優勢呢?Zuul(1.x) 基於 Servlet,使用阻塞 API,它不支援任何長連線,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支援 WebSockets,支援限流等新特性。

Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud 的一個全新專案,該專案是基於 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術開發的閘道器,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。

Spring Cloud Gateway 作為 Spring Cloud 生態系統中的閘道器,目標是替代 Netflix Zuul,其不僅提供統一的路由方式,並且基於 Filter 鏈的方式提供了閘道器基本的功能,例如:安全,監控/指標,和限流。

相關概念:

  • Route(路由):這是閘道器的基本構建塊。它由一個 ID,一個目標 URI,一組斷言和一組過濾器定義。如果斷言為真,則路由匹配。
  • Predicate(斷言):這是一個 Java 8 的 Predicate。輸入型別是一個 ServerWebExchange。我們可以使用它來匹配來自 HTTP 請求的任何內容,例如 headers 或引數。
  • Filter(過濾器):這是org.springframework.cloud.gateway.filter.GatewayFilter的例項,我們可以使用它修改請求和響應。

工作流程:

springcloud(十五):Spring Cloud 終於按捺不住推出了自己的服務閘道器 Gateway

客戶端向 Spring Cloud Gateway 發出請求。如果 Gateway Handler Mapping 中找到與請求相匹配的路由,將其傳送到 Gateway Web Handler。Handler 再通過指定的過濾器鏈來將請求傳送到我們實際的服務執行業務邏輯,然後返回。
過濾器之間用虛線分開是因為過濾器可能會在傳送代理請求之前(“pre”)或之後(“post”)執行業務邏輯。

Spring Cloud Gateway 的特徵:

  • 基於 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
  • 動態路由
  • Predicates 和 Filters 作用於特定路由
  • 整合 Hystrix 斷路器
  • 整合 Spring Cloud DiscoveryClient
  • 易於編寫的 Predicates 和 Filters
  • 限流
  • 路徑重寫

快速上手

Spring Cloud Gateway 閘道器路由有兩種配置方式:

  • 在配置檔案 yml 中配置
  • 通過@Bean自定義 RouteLocator,在啟動主類 Application 中配置

這兩種方式是等價的,建議使用 yml 方式進配置。

使用 Spring Cloud Finchley 版本,Finchley 版本依賴於 Spring Boot 2.0.6.RELEASE。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.6.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Finchley.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

經測試 Finchley.RELEASE 有 bug 多次請求會報空指標異常,SR2 是 Spring Cloud 的最新版本。

新增專案需要使用的依賴包

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

Spring Cloud Gateway 是使用 netty+webflux 實現因此不需要再引入 web 模組。

我們先來測試一個最簡單的請求轉發。

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      - id: neo_route
        uri: http://www.ityouknow.com
        predicates:
        - Path=/spring-cloud

各欄位含義如下:

  • id:我們自定義的路由 ID,保持唯一
  • uri:目標服務地址
  • predicates:路由條件,Predicate 接受一個輸入引數,返回一個布林值結果。該介面包含多種預設方法來將 Predicate 組合成其他複雜的邏輯(比如:與,或,非)。
  • filters:過濾規則,本示例暫時沒用。

上面這段配置的意思是,配置了一個 id 為 neo_route 的路由規則,當訪問地址 http://localhost:8080/spring-cloud時會自動轉發到地址:http://www.ityouknow.com/spring-cloud。配置完成啟動專案即可在瀏覽器訪問進行測試,當我們訪問地址http://localhost:8080/spring-cloud 時會展示頁面展示如下:

springcloud(十五):Spring Cloud 終於按捺不住推出了自己的服務閘道器 Gateway

證明頁面轉發成功。

轉發功能同樣可以通過程式碼來實現,我們可以在啟動類 GateWayApplication 中新增方法 customRouteLocator() 來定製轉發規則。

@SpringBootApplication
public class GateWayApplication {

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

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/about")
                        .uri("http://ityouknow.com"))
                .build();
    }

}

上面配置了一個 id 為 path_route 的路由,當訪問地址http://localhost:8080/about時會自動轉發到地址:http://www.ityouknow.com/about和上面的轉發效果一樣,只是這裡轉發的是以專案地址/about格式的請求地址。

上面兩個示例中 uri 都是指向了我的個人網站,在實際專案使用中可以將 uri 指向對外提供服務的專案地址,統一對外輸出介面。

以上便是 Spring Cloud Gateway 最簡單的兩個請求示例,Spring Cloud Gateway 還有更多實用的功能接下來我們一一介紹。

路由規則

Spring Cloud Gateway 的功能很強大,我們僅僅通過 Predicates 的設計就可以看出來,前面我們只是使用了 predicates 進行了簡單的條件匹配,其實 Spring Cloud Gataway 幫我們內建了很多 Predicates 功能。

Spring Cloud Gateway 是通過 Spring WebFlux 的 HandlerMapping 做為底層支援來匹配到轉發路由,Spring Cloud Gateway 內建了很多 Predicates 工廠,這些 Predicates 工廠通過不同的 HTTP 請求引數來匹配,多個 Predicates 工廠可以組合使用。

Predicate 介紹

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

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

springcloud(十五):Spring Cloud 終於按捺不住推出了自己的服務閘道器 Gateway

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

通過時間匹配

Predicate 支援設定一個時間,在請求進行轉發的時候,可以通過判斷在這個時間之前或者之後進行轉發。比如我們現在設定只有在2019年1月1日才會轉發到我的網站,在這之前不進行轉發,我就可以這樣配置:

spring:
  cloud:
    gateway:
      routes:
       - id: time_route
        uri: http://ityouknow.com
        predicates:
         - After=2018-01-20T06:06:06+08:00[Asia/Shanghai]

Spring 是通過 ZonedDateTime 來對時間進行的對比,ZonedDateTime 是 Java 8 中日期時間功能裡,用於表示帶時區的日期與時間資訊的類,ZonedDateTime 支援通過時區來設定時間,中國的時區是:Asia/Shanghai

After Route Predicate 是指在這個時間之後的請求都轉發到目標地址。上面的示例是指,請求時間在 2018年1月20日6點6分6秒之後的所有請求都轉發到地址http://ityouknow.com+08:00是指時間和UTC時間相差八個小時,時間地區為Asia/Shanghai

新增完路由規則之後,訪問地址http://localhost:8080會自動轉發到http://ityouknow.com

Before Route Predicate 剛好相反,在某個時間之前的請求的請求都進行轉發。我們把上面路由規則中的 After 改為 Before,如下:

spring:
  cloud:
    gateway:
      routes:
       - id: after_route
        uri: http://ityouknow.com
        predicates:
         - Before=2018-01-20T06:06:06+08:00[Asia/Shanghai]

就表示在這個時間之前可以進行路由,在這時間之後停止路由,修改完之後重啟專案再次訪問地址http://localhost:8080,頁面會報 404 沒有找到地址。

除過在時間之前或者之後外,Gateway 還支援限制路由請求在某一個時間段範圍內,可以使用 Between Route Predicate 來實現。

spring:
  cloud:
    gateway:
      routes:
       - id: after_route
        uri: http://ityouknow.com
        predicates:
         - Between=2018-01-20T06:06:06+08:00[Asia/Shanghai], 2019-01-20T06:06:06+08:00[Asia/Shanghai]

這樣設定就意味著在這個時間段內可以匹配到此路由,超過這個時間段範圍則不會進行匹配。通過時間匹配路由的功能很酷,可以用在限時搶購的一些場景中。

Cookie Route Predicate 可以接收兩個引數,一個是 Cookie name ,一個是正規表示式,路由規則會通過獲取對應的 Cookie name 值和正規表示式去匹配,如果匹配上就會執行路由,如果沒有匹配上則不執行。

spring:
  cloud:
    gateway:
      routes:
         - id: cookie_route
              uri: http://ityouknow.com
              predicates:
              - Cookie=ityouknow, kee.e

使用 curl 測試,命令列輸入:

curl http://localhost:8080 --cookie "ityouknow=kee.e"

則會返回頁面程式碼,如果去掉--cookie "ityouknow=kee.e",後臺彙報 404 錯誤。

通過 Header 屬性匹配

Header Route Predicate 和 Cookie Route Predicate 一樣,也是接收 2 個引數,一個 header 中屬性名稱和一個正規表示式,這個屬性值和正規表示式匹配則執行。

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: http://ityouknow.com
        predicates:
        - Header=X-Request-Id, \d+

使用 curl 測試,命令列輸入:

curl http://localhost:8080  -H "X-Request-Id:666666" 

則返回頁面程式碼證明匹配成功。將引數-H "X-Request-Id:666666"改為-H "X-Request-Id:neo"再次執行時返回404證明沒有匹配。

通過 Host 匹配

Host Route Predicate 接收一組引數,一組匹配的域名列表,這個模板是一個 ant 分隔的模板,用.號作為分隔符。它通過引數中的主機地址作為匹配規則。

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://ityouknow.com
        predicates:
        - Host=**.ityouknow.com

使用 curl 測試,命令列輸入:

curl http://localhost:8080  -H "Host: www.ityouknow.com" 
curl http://localhost:8080  -H "Host: md.ityouknow.com" 

經測試以上兩種 host 均可匹配到 host_route 路由,去掉 host 引數則會報 404 錯誤。

通過請求方式匹配

可以通過是 POST、GET、PUT、DELETE 等不同的請求方式來進行路由。

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: http://ityouknow.com
        predicates:
        - Method=GET

使用 curl 測試,命令列輸入:

# curl 預設是以 GET 的方式去請求
curl http://localhost:8080

測試返回頁面程式碼,證明匹配到路由,我們再以 POST 的方式請求測試。

# curl 預設是以 GET 的方式去請求
curl -X POST http://localhost:8080

返回 404 沒有找到,證明沒有匹配上路由

通過請求路徑匹配

Path Route Predicate 接收一個匹配路徑的引數來判斷是否走路由。

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: http://ityouknow.com
        predicates:
        - Path=/foo/{segment}

如果請求路徑符合要求,則此路由將匹配,例如:/foo/1 或者 /foo/bar。

使用 curl 測試,命令列輸入:

curl http://localhost:8080/foo/1
curl http://localhost:8080/foo/xx
curl http://localhost:8080/boo/xx

經過測試第一和第二條命令可以正常獲取到頁面返回值,最後一個命令報404,證明路由是通過指定路由來匹配。

通過請求引數匹配

Query Route Predicate 支援傳入兩個引數,一個是屬性名一個為屬性值,屬性值可以是正規表示式。

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://ityouknow.com
        predicates:
        - Query=smile

這樣配置,只要請求中包含 smile 屬性的引數即可匹配路由。

使用 curl 測試,命令列輸入:

curl localhost:8080?smile=x&id=2

經過測試發現只要請求彙總帶有 smile 引數即會匹配路由,不帶 smile 引數則不會匹配。

還可以將 Query 的值以鍵值對的方式進行配置,這樣在請求過來時會對屬性值和正則進行匹配,匹配上才會走路由。

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://ityouknow.com
        predicates:
        - Query=keep, pu.

這樣只要當請求中包含 keep 屬性並且引數值是以 pu 開頭的長度為三位的字串才會進行匹配和路由。

使用 curl 測試,命令列輸入:

curl localhost:8080?keep=pub

測試可以返回頁面程式碼,將 keep 的屬性值改為 pubx 再次訪問就會報 404,證明路由需要匹配正規表示式才會進行路由。

通過請求 ip 地址進行匹配

Predicate 也支援通過設定某個 ip 區間號段的請求才會路由,RemoteAddr Route Predicate 接受 cidr 符號(IPv4 或 IPv6 )字串的列表(最小大小為1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址,16 是子網掩碼)。

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: http://ityouknow.com
        predicates:
        - RemoteAddr=192.168.1.1/24

可以將此地址設定為本機的 ip 地址進行測試。

curl localhost:8080

果請求的遠端地址是 192.168.1.10,則此路由將匹配。

組合使用

上面為了演示各個 Predicate 的使用,我們是單個單個進行配置測試,其實可以將各種 Predicate 組合起來一起使用。

例如:

spring:
  cloud:
    gateway:
      routes:
       - id: host_foo_path_headers_to_httpbin
        uri: http://ityouknow.com
        predicates:
        - Host=**.foo.org
        - Path=/headers
        - Method=GET
        - Header=X-Request-Id, \d+
        - Query=foo, ba.
        - Query=baz
        - Cookie=chocolate, ch.p
        - After=2018-01-20T06:06:06+08:00[Asia/Shanghai]

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

一個請求滿足多個路由的謂詞條件時,請求只會被首個成功匹配的路由轉發

總結

通過今天的學習發現 Spring Cloud Gateway 使用非常的靈活,可以根據不同的情況來進行路由分發,在實際專案中可以自由組合使用。同時 Spring Cloud Gateway 還有更多很酷的功能,比如 Filter 、熔斷和限流等,下次我們繼續學習 Spring Cloud Gateway 的高階功能。

示例程式碼-github

示例程式碼-碼雲

參考

Spring Cloud Gateway
Spring Cloud Gateway(路由)

相關文章