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