Spring Cloud 終于按捺不住推出了自己的服務(wù)網(wǎng)關(guān) Gateway
Spring 官方最終還是按捺不住推出了自己的網(wǎng)關(guān)組件:Spring Cloud Gateway ,相比之前我們使用的 Zuul(1.x) 它有哪些優(yōu)勢呢?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 等技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。
Spring Cloud Gateway 作為 Spring Cloud 生態(tài)系統(tǒng)中的網(wǎng)關(guān),目標是替代 Netflix Zuul,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了網(wǎng)關(guān)基本的功能,例如:安全,監(jiān)控/指標,和限流。
相關(guān)概念:
- Route(路由):這是網(wǎng)關(guān)的基本構(gòu)建塊。它由一個 ID,一個目標 URI,一組斷言和一組過濾器定義。如果斷言為真,則路由匹配。
- Predicate(斷言):這是一個 Java 8 的 Predicate。輸入類型是一個 ServerWebExchange。我們可以使用它來匹配來自 HTTP 請求的任何內(nèi)容,例如 headers 或參數(shù)。
- Filter(過濾器):這是org.springframework.cloud.gateway.filter.GatewayFilter的實例,我們可以使用它修改請求和響應(yīng)。
工作流程:
客戶端向 Spring Cloud Gateway 發(fā)出請求。如果 Gateway Handler Mapping 中找到與請求相匹配的路由,將其發(fā)送到 Gateway Web Handler。Handler 再通過指定的過濾器鏈來將請求發(fā)送到我們實際的服務(wù)執(zhí)行業(yè)務(wù)邏輯,然后返回。
過濾器之間用虛線分開是因為過濾器可能會在發(fā)送代理請求之前(“pre”)或之后(“post”)執(zhí)行業(yè)務(wù)邏輯。
Spring Cloud Gateway 的特征:
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 動態(tài)路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 斷路器
- 集成 Spring Cloud DiscoveryClient
- 易于編寫的 Predicates 和 Filters
- 限流
- 路徑重寫
快速上手
Spring Cloud Gateway 網(wǎng)關(guān)路由有兩種配置方式:
- 在配置文件 yml 中配置
- 通過@Bean自定義 RouteLocator,在啟動主類 Application 中配置
這兩種方式是等價的,建議使用 yml 方式進配置。
使用 Spring Cloud Finchley 版本,F(xiàn)inchley 版本依賴于 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>
經(jīng)測試 Finchley.RELEASE 有 bug 多次請求會報空指針異常,SR2 是 Spring Cloud 的***版本。
添加項目需要使用的依賴包
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
Spring Cloud Gateway 是使用 netty+webflux 實現(xiàn)因此不需要再引入 web 模塊。
我們先來測試一個最簡單的請求轉(zhuǎn)發(fā)。
- server:
- port: 8080
- spring:
- cloud:
- gateway:
- routes:
- - id: neo_route
- uri: http://www.ityouknow.com
- predicates:
- - Path=/spring-cloud
各字段含義如下:
- id:我們自定義的路由 ID,保持唯一
- uri:目標服務(wù)地址
- predicates:路由條件,Predicate 接受一個輸入?yún)?shù),返回一個布爾值結(jié)果。該接口包含多種默認方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)。
- filters:過濾規(guī)則,本示例暫時沒用。
上面這段配置的意思是,配置了一個 id 為 neo_route 的路由規(guī)則,當訪問地址 http://localhost:8080/spring-cloud時會自動轉(zhuǎn)發(fā)到地址:http://www.ityouknow.com/spring-cloud。配置完成啟動項目即可在瀏覽器訪問進行測試,當我們訪問地址http://localhost:8080/spring-cloud 時會展示頁面展示如下:
明頁面轉(zhuǎn)發(fā)成功。
轉(zhuǎn)發(fā)功能同樣可以通過代碼來實現(xiàn),我們可以在啟動類 GateWayApplication 中添加方法 customRouteLocator() 來定制轉(zhuǎn)發(fā)規(guī)則。
- @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時會自動轉(zhuǎn)發(fā)到地址:http://www.ityouknow.com/about和上面的轉(zhuǎn)發(fā)效果一樣,只是這里轉(zhuǎn)發(fā)的是以項目地址/about格式的請求地址。
上面兩個示例中 uri 都是指向了我的個人網(wǎng)站,在實際項目使用中可以將 uri 指向?qū)ν馓峁┓?wù)的項目地址,統(tǒng)一對外輸出接口。
以上便是 Spring Cloud Gateway 最簡單的兩個請求示例,Spring Cloud Gateway 還有更多實用的功能接下來我們一一介紹。
路由規(guī)則
Spring Cloud Gateway 的功能很強大,我們僅僅通過 Predicates 的設(shè)計就可以看出來,前面我們只是使用了 predicates 進行了簡單的條件匹配,其實 Spring Cloud Gataway 幫我們內(nèi)置了很多 Predicates 功能。
Spring Cloud Gateway 是通過 Spring WebFlux 的 HandlerMapping 做為底層支持來匹配到轉(zhuǎn)發(fā)路由,Spring Cloud Gateway 內(nèi)置了很多 Predicates 工廠,這些 Predicates 工廠通過不同的 HTTP 請求參數(shù)來匹配,多個 Predicates 工廠可以組合使用。
Predicate 介紹
Predicate 來源于 Java 8,是 Java 8 中引入的一個函數(shù),Predicate 接受一個輸入?yún)?shù),返回一個布爾值結(jié)果。該接口包含多種默認方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)??梢杂糜诮涌谡埱髤?shù)校驗、判斷新老數(shù)據(jù)是否有變化需要進行更新操作。and--與、or--或、negate--非
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實現(xiàn)了各種路由匹配規(guī)則,有通過 Header、請求參數(shù)等不同的條件來進行作為條件匹配到對應(yīng)的路由。網(wǎng)上有一張圖總結(jié)了 Spring Cloud 內(nèi)置的幾種 Predicate 的實現(xiàn)。
說白了 Predicate 就是為了實現(xiàn)一組匹配規(guī)則,方便讓請求過來找到對應(yīng)的 Route 進行處理,接下來我們接下 Spring Cloud GateWay 內(nèi)置幾種 Predicate 的使用。
通過時間匹配
Predicate 支持設(shè)置一個時間,在請求進行轉(zhuǎn)發(fā)的時候,可以通過判斷在這個時間之前或者之后進行轉(zhuǎn)發(fā)。比如我們現(xiàn)在設(shè)置只有在2019年1月1日才會轉(zhuǎn)發(fā)到我的網(wǎng)站,在這之前不進行轉(zhuǎn)發(fā),我就可以這樣配置:
- 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 中日期時間功能里,用于表示帶時區(qū)的日期與時間信息的類,ZonedDateTime 支持通過時區(qū)來設(shè)置時間,中國的時區(qū)是:Asia/Shanghai。
After Route Predicate 是指在這個時間之后的請求都轉(zhuǎn)發(fā)到目標地址。上面的示例是指,請求時間在 2018年1月20日6點6分6秒之后的所有請求都轉(zhuǎn)發(fā)到地址http://ityouknow.com。+08:00是指時間和UTC時間相差八個小時,時間地區(qū)為Asia/Shanghai。
添加完路由規(guī)則之后,訪問地址http://localhost:8080會自動轉(zhuǎn)發(fā)到http://ityouknow.com。
Before Route Predicate 剛好相反,在某個時間之前的請求的請求都進行轉(zhuǎn)發(fā)。我們把上面路由規(guī)則中的 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 還支持限制路由請求在某一個時間段范圍內(nèi),可以使用 Between Route Predicate 來實現(xiàn)。
- 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]
這樣設(shè)置就意味著在這個時間段內(nèi)可以匹配到此路由,超過這個時間段范圍則不會進行匹配。通過時間匹配路由的功能很酷,可以用在限時搶購的一些場景中。
通過 Cookie 匹配
Cookie Route Predicate 可以接收兩個參數(shù),一個是 Cookie name ,一個是正則表達式,路由規(guī)則會通過獲取對應(yīng)的 Cookie name 值和正則表達式去匹配,如果匹配上就會執(zhí)行路由,如果沒有匹配上則不執(zhí)行。
- 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 個參數(shù),一個 header 中屬性名稱和一個正則表達式,這個屬性值和正則表達式匹配則執(zhí)行。
- 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"
則返回頁面代碼證明匹配成功。將參數(shù)-H "X-Request-Id:666666"改為-H "X-Request-Id:neo"再次執(zhí)行時返回404證明沒有匹配。
通過 Host 匹配
Host Route Predicate 接收一組參數(shù),一組匹配的域名列表,這個模板是一個 ant 分隔的模板,用.號作為分隔符。它通過參數(shù)中的主機地址作為匹配規(guī)則。
- 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"
經(jīng)測試以上兩種 host 均可匹配到 host_route 路由,去掉 host 參數(shù)則會報 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 接收一個匹配路徑的參數(shù)來判斷是否走路由。
- 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
經(jīng)過測試***和第二條命令可以正常獲取到頁面返回值,***一個命令報404,證明路由是通過指定路由來匹配。
通過請求參數(shù)匹配
Query Route Predicate 支持傳入兩個參數(shù),一個是屬性名一個為屬性值,屬性值可以是正則表達式。
- spring:
- cloud:
- gateway:
- routes:
- - id: query_route
- uri: http://ityouknow.com
- predicates:
- - Query=smile
這樣配置,只要請求中包含 smile 屬性的參數(shù)即可匹配路由。
使用 curl 測試,命令行輸入:
- curl localhost:8080?smile=x&id=2
經(jīng)過測試發(fā)現(xiàn)只要請求匯總帶有 smile 參數(shù)即會匹配路由,不帶 smile 參數(shù)則不會匹配。
還可以將 Query 的值以鍵值對的方式進行配置,這樣在請求過來時會對屬性值和正則進行匹配,匹配上才會走路由。
- spring:
- cloud:
- gateway:
- routes:
- - id: query_route
- uri: http://ityouknow.com
- predicates:
- - Query=keep, pu.
這樣只要當請求中包含 keep 屬性并且參數(shù)值是以 pu 開頭的長度為三位的字符串才會進行匹配和路由。
使用 curl 測試,命令行輸入:
- curl localhost:8080?keep=pub
測試可以返回頁面代碼,將 keep 的屬性值改為 pubx 再次訪問就會報 404,證明路由需要匹配正則表達式才會進行路由。
通過請求 ip 地址進行匹配
Predicate 也支持通過設(shè)置某個 ip 區(qū)間號段的請求才會路由,RemoteAddr Route Predicate 接受 cidr 符號(IPv4 或 IPv6 )字符串的列表(最小大小為1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址,16 是子網(wǎng)掩碼)。
- spring:
- cloud:
- gateway:
- routes:
- - id: remoteaddr_route
- uri: http://ityouknow.com
- predicates:
- - RemoteAddr=192.168.1.1/24
可以將此地址設(shè)置為本機的 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 同時存在于同一個路由時,請求必須同時滿足所有的條件才被這個路由匹配。
一個請求滿足多個路由的謂詞條件時,請求只會被***成功匹配的路由轉(zhuǎn)發(fā)
總結(jié)
通過今天的學(xué)習發(fā)現(xiàn) Spring Cloud Gateway 使用非常的靈活,可以根據(jù)不同的情況來進行路由分發(fā),在實際項目中可以自由組合使用。同時 Spring Cloud Gateway 還有更多很酷的功能,比如 Filter 、熔斷和限流等,下次我們繼續(xù)學(xué)習 Spring Cloud Gateway 的高級功能。
所有代碼都在這里:https://github.com/ityouknow/spring-cloud-examples
【本文為51CTO專欄作者“純潔的微笑”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】