前面我們在聊服務網關Zuul的時候提到了Gateway,那麼Zuul和Gateway都是服務網關,這兩個有什麼區別呢?
1. Zuul和Gateway的恩怨情仇
1.1 背景
Zuul是Netflix開源的一個項目,Spring只是將Zuul集成在了Spring Cloud中。而Spring Cloud Gateway是Spring Cloud的一個子項目。
還有一個版本的說法是Zuul2的連續跳票和Zuul1的性能並不是很理想,從而催生了Spring Cloud Gateway。
1.2 性能比較
網上很多地方都說Zuul是阻塞的,Gateway是非阻塞的,這麼說是不嚴謹的,準確的講Zuul1.x是阻塞的,而在2.x的版本中,Zuul也是基於Netty,也是非阻塞的,如果一定要說性能,其實這個真沒多大差距。
而官方出過一個測試項目,創建了一個benchmark的測試項目:spring-cloud-gateway-bench,其中對比了:
- Spring Cloud Gateway
- Zuul1.x
- Linkerd
組件 | RPS(request per second) |
---|---|
Spring Cloud Gateway | Requests/sec: 32213.38 |
Zuul | Requests/sec: 20800.13 |
Linkerd | Requests/sec: 28050.76 |
從結果可知,Spring Cloud Gateway的RPS是Zuul1.x的1.6倍。
下面,我們進入正題,開始聊聊Spring Cloud Gateway的一些事情。
2. 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 鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。
2.1 特徵
-
基於 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
-
動態路由
-
Predicates 和 Filters 作用於特定路由
-
集成 Hystrix 斷路器
-
集成 Spring Cloud DiscoveryClient
-
易於編寫的 Predicates 和 Filters
-
限流
-
路徑重寫
2.2 術語
-
Route(路由):這是網關的基本構建塊。它由一個 ID,一個目標 URI,一組斷言和一組過濾器定義。如果斷言爲真,則路由匹配。
-
Predicate(斷言):這是一個 Java 8 的 Predicate。輸入類型是一個 ServerWebExchange。我們可以使用它來匹配來自 HTTP 請求的任何內容,例如 headers 或參數。
-
Filter(過濾器):這是org.springframework.cloud.gateway.filter.GatewayFilter的實例,我們可以使用它修改請求和響應。
2.3 流程
客戶端向 Spring Cloud Gateway 發出請求。然後在 Gateway Handler Mapping 中找到與請求相匹配的路由,將其發送到 Gateway Web Handler。Handler 再通過指定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯,然後返回。過濾器之間用虛線分開是因爲過濾器可能會在發送代理請求之前(“pre”)或之後(“post”)執行業務邏輯。
3.快速上手
Spring Cloud Gateway 網關路由有兩種配置方式:
-
在配置文件 yml 中配置
-
通過@Bean自定義 RouteLocator,在啓動主類 Application 中配置
這兩種方式是等價的,建議使用 yml 方式進配置。
此項目是前面項目的一個模塊,生成的pom文件爲:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>wm-spring-cloud</artifactId> <groupId>com.xuan.test.springcloud</groupId> <version>0.0.1</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>gateway</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Spring Cloud Gateway 是使用 netty+webflux 實現因此不需要再引入 web 模塊
3.2 配置文件
通過配置文件來設置規則,application.yml文件內容爲:
server:
port: 9000
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
- id: test_1
uri: https://www.sina.com.cn
stripPrefix: 1
predicates:
- Path=/sina
各字段含義如下:
- id:我們自定義的路由 ID,保持唯一
- uri:目標服務地址
- predicates:路由條件,Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他複雜的邏輯(比如:與,或,非)。
上面這段配置的意思是,配置了一個 id 爲 gateway-service 的路由規則,當訪問地址 http://localhost:8080/meteor_93時會自動轉發到地址:http://localhost:8080/meteor_93。
- 注意: 這裏的配置和Zuul路由的配置規則是不一致的,具體的可以去查看SpringCloud之網關 Zuul(四)
通過@Bean自定義 RouteLocator,GatewayApplication.java內容:
@SpringBootApplication public class GatewayApplication { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/163") .filters(f -> f.stripPrefix(1)) .uri("https://www.163.com")) .build(); } public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
3.3 測試
現在我們啓動服務,在瀏覽器中訪問地址http://localhost:9000/163和http://localhost:9000/sina時就會跳轉到對應的頁面。
4. 路由規則
Spring Cloud Gateway 的功能很強大,我們僅僅通過 Predicates 的設計就可以看出來,前面我們只是使用了 predicates 進行了簡單的條件匹配,其實 Spring Cloud Gataway 幫我們內置了很多 Predicates 功能。
Spring Cloud Gateway 是通過 Spring WebFlux 的 HandlerMapping 做爲底層支持來匹配到轉發路由,Spring Cloud Gateway 內置了很多 Predicates 工廠,這些 Predicates 工廠通過不同的 HTTP 請求參數來匹配,多個 Predicates 工廠可以組合使用。
4.1 Predicate 介紹
Predicate 來源於 Java 8,是 Java 8 中引入的一個函數,Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將 Predicate 組合成其他複雜的邏輯(比如:與,或,非)。可以用於接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實現了各種路由匹配規則,有通過 Header、請求參數等不同的條件來進行作爲條件匹配到對應的路由。網上有一張圖總結了 Spring Cloud 內置的幾種 Predicate 的實現。
說白了 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 匹配
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 的高級功能