1. Zuul和Gateway的恩怨情仇
1.1 背景
Zuul是Netflix開源的一個(gè)項(xiàng)目,Spring只是將Zuul集成在了Spring Cloud中愉择。而Spring Cloud Gateway是Spring Cloud的一個(gè)子項(xiàng)目锥涕。
還有一個(gè)版本的說法是Zuul2的連續(xù)跳票和Zuul1的性能并不是很理想,從而催生了Spring Cloud Gateway站楚。
1.2 性能比較
網(wǎng)上很多地方都說Zuul是阻塞的,Gateway是非阻塞的拉一,這么說是不嚴(yán)謹(jǐn)?shù)木善颍瑴?zhǔn)確的講Zuul1.x是阻塞的,而在2.x的版本中嫡纠,Zuul也是基于Netty延赌,也是非阻塞的,如果一定要說性能者蠕,其實(shí)這個(gè)真沒多大差距掐松。(了解源碼可+求求: 1791743380)
而官方出過一個(gè)測試項(xiàng)目,創(chuàng)建了一個(gè)benchmark的測試項(xiàng)目:spring-cloud-gateway-bench抡句,其中對比了:
Spring Cloud Gateway
Zuul1.x
Linkerd
組件RPS(request per second)
Spring Cloud GatewayRequests/sec: 32213.38
ZuulRequests/sec: 20800.13
LinkerdRequests/sec: 28050.76
從結(jié)果可知杠愧,Spring Cloud Gateway的RPS是Zuul1.x的1.6倍。
下面究抓,我們進(jìn)入正題袭灯,開始聊聊Spring Cloud Gateway的一些事情。
2. Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目橘茉,該項(xiàng)目是基于 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)歼争,目標(biāo)是替代 Netflix Zuul,其不僅提供統(tǒng)一的路由方式沐绒,并且基于 Filter 鏈的方式提供了網(wǎng)關(guān)基本的功能乔遮,例如:安全,監(jiān)控/指標(biāo)蹋肮,和限流坯辩。
2.1 特征
基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
動(dòng)態(tài)路由
Predicates 和 Filters 作用于特定路由
集成 Hystrix 斷路器
集成 Spring Cloud DiscoveryClient
易于編寫的 Predicates 和 Filters
限流
路徑重寫
2.2 術(shù)語
Route(路由):這是網(wǎng)關(guān)的基本構(gòu)建塊濒翻。它由一個(gè) ID有送,一個(gè)目標(biāo) URI,一組斷言和一組過濾器定義雀摘。如果斷言為真,則路由匹配涯塔。
Predicate(斷言):這是一個(gè) Java 8 的 Predicate。輸入類型是一個(gè) ServerWebExchange爹谭。我們可以使用它來匹配來自 HTTP 請求的任何內(nèi)容榛搔,例如 headers 或參數(shù)。
Filter(過濾器):這是org.springframework.cloud.gateway.filter.GatewayFilter的實(shí)例腹泌,我們可以使用它修改請求和響應(yīng)尔觉。
2.3 流程
客戶端向 Spring Cloud Gateway 發(fā)出請求。然后在 Gateway Handler Mapping 中找到與請求相匹配的路由专甩,將其發(fā)送到 Gateway Web Handler泵额。Handler 再通過指定的過濾器鏈來將請求發(fā)送到我們實(shí)際的服務(wù)執(zhí)行業(yè)務(wù)邏輯,然后返回篓叶。過濾器之間用虛線分開是因?yàn)檫^濾器可能會(huì)在發(fā)送代理請求之前(“pre”)或之后(“post”)執(zhí)行業(yè)務(wù)邏輯羞秤。
3.快速上手
Spring Cloud Gateway 網(wǎng)關(guān)路由有兩種配置方式:
在配置文件 yml 中配置
通過@Bean自定義 RouteLocator,在啟動(dòng)主類 Application 中配置
這兩種方式是等價(jià)的俐镐,建議使用 yml 方式進(jìn)配置哺哼。
3.1 項(xiàng)目依賴
<?xml version="1.0" encoding="UTF-8"?>4.0.0org.springframework.bootspring-boot-starter-parent2.1.6.RELEASE<!-- lookup parent from repository -->com.springcloudgateway0.0.1-SNAPSHOTgatewayDemo project for Spring Boot1.8Greenwich.SR1org.springframework.cloudspring-cloud-starter-gatewayorg.springframework.bootspring-boot-starter-testtestorg.springframework.cloudspring-cloud-dependencies${spring-cloud.version}pomimportorg.springframework.bootspring-boot-maven-plugin
Spring Cloud Gateway 是使用 netty+webflux 實(shí)現(xiàn)因此不需要再引入 web 模塊取董。
3.2 配置文件
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://blog.csdn.net? ? ? ? ? predicates:? ? ? ? ? ? -Path=/meteor_93COPY
各字段含義如下:
id:我們自定義的路由 ID,保持唯一
uri:目標(biāo)服務(wù)地址
predicates:路由條件枢里,Predicate 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果栏豺。該接口包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與奥洼,或,非)溉卓。
上面這段配置的意思是搬泥,配置了一個(gè) id 為 gateway-service 的路由規(guī)則忿檩,當(dāng)訪問地址?http://localhost:8080/meteor_93時(shí)會(huì)自動(dòng)轉(zhuǎn)發(fā)到地址:http://localhost:8080/meteor_93。
3.3 測試
現(xiàn)在我們啟動(dòng)服務(wù)燥透,在瀏覽器中訪問地址http://localhost:8080/meteor_93 時(shí)會(huì)展示頁面展示如下:
證明頁面轉(zhuǎn)發(fā)成功班套。
3.4 另一種路由配置方式
轉(zhuǎn)發(fā)功能同樣可以通過代碼來實(shí)現(xiàn),我們可以在啟動(dòng)類 GateWayApplication 中添加方法 customRouteLocator() 來定制轉(zhuǎn)發(fā)規(guī)則吆豹。
packagecom.springcloud.gateway;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.cloud.gateway.route.RouteLocator;importorg.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;importorg.springframework.context.annotation.Bean;@SpringBootApplicationpublicclassGatewayApplication{publicstaticvoidmain(String[] args){? ? ? ? SpringApplication.run(GatewayApplication.class, args);? ? }@BeanpublicRouteLocatorcustomRouteLocator(RouteLocatorBuilder builder){returnbuilder.routes()? ? ? ? ? ? ? ? .route("path_route", r -> r.path("/meteor_93")? ? ? ? ? ? ? ? ? ? ? ? .uri("https://blog.csdn.net"))? ? ? ? ? ? ? ? .build();? ? }}
我們在yaml配置文件中注銷掉相關(guān)路由的配置理盆,重啟服務(wù),訪問鏈接:http://localhost:8080/meteor_93衷快, 可以看到和上面一樣的頁面姨俩,證明我們測試成功。
上面兩個(gè)示例中 uri 都是指向了我的CSDN博客调窍,在實(shí)際項(xiàng)目使用中可以將 uri 指向?qū)ν馓峁┓?wù)的項(xiàng)目地址积担,統(tǒng)一對外輸出接口。
以上便是 Spring Cloud Gateway 最簡單的兩個(gè)請求示例先誉,Spring Cloud Gateway 還有更多實(shí)用的功能接下來我們一一介紹。
4. 路由規(guī)則
Spring Cloud Gateway 的功能很強(qiáng)大诈闺,我們僅僅通過 Predicates 的設(shè)計(jì)就可以看出來铃芦,前面我們只是使用了 predicates 進(jìn)行了簡單的條件匹配,其實(shí) Spring Cloud Gataway 幫我們內(nèi)置了很多 Predicates 功能仁烹。
Spring Cloud Gateway 是通過 Spring WebFlux 的 HandlerMapping 做為底層支持來匹配到轉(zhuǎn)發(fā)路由咧虎,Spring Cloud Gateway 內(nèi)置了很多 Predicates 工廠,這些 Predicates 工廠通過不同的 HTTP 請求參數(shù)來匹配征唬,多個(gè) Predicates 工廠可以組合使用茁彭。
4.1 Predicate 介紹
Predicate 來源于 Java 8,是 Java 8 中引入的一個(gè)函數(shù)摄闸,Predicate 接受一個(gè)輸入?yún)?shù)哲嘲,返回一個(gè)布爾值結(jié)果。該接口包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與画切,或囱怕,非)⊥薰可以用于接口請求參數(shù)校驗(yàn)台丛、判斷新老數(shù)據(jù)是否有變化需要進(jìn)行更新操作砾肺。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實(shí)現(xiàn)了各種路由匹配規(guī)則防嗡,有通過 Header、請求參數(shù)等不同的條件來進(jìn)行作為條件匹配到對應(yīng)的路由裙盾。網(wǎng)上有一張圖總結(jié)了 Spring Cloud 內(nèi)置的幾種 Predicate 的實(shí)現(xiàn)他嫡。
說白了 Predicate 就是為了實(shí)現(xiàn)一組匹配規(guī)則,方便讓請求過來找到對應(yīng)的 Route 進(jìn)行處理徘熔,接下來我們接下 Spring Cloud GateWay 內(nèi)置幾種 Predicate 的使用。
4.2 通過時(shí)間匹配
Predicate 支持設(shè)置一個(gè)時(shí)間盖高,在請求進(jìn)行轉(zhuǎn)發(fā)的時(shí)候副渴,可以通過判斷在這個(gè)時(shí)間之前或者之后進(jìn)行轉(zhuǎn)發(fā)。比如我們現(xiàn)在設(shè)置只有在2019年1月1日才會(huì)轉(zhuǎn)發(fā)到我的博客,在這之前不進(jìn)行轉(zhuǎn)發(fā)域醇,我就可以這樣配置:
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -After=2019-01-01T00:00:00+08:00[Asia/Shanghai]
Spring 是通過 ZonedDateTime 來對時(shí)間進(jìn)行的對比譬挚,ZonedDateTime 是 Java 8 中日期時(shí)間功能里,用于表示帶時(shí)區(qū)的日期與時(shí)間信息的類盐须,ZonedDateTime 支持通過時(shí)區(qū)來設(shè)置時(shí)間漆腌,中國的時(shí)區(qū)是:Asia/Shanghai。
After Route Predicate 是指在這個(gè)時(shí)間之后的請求都轉(zhuǎn)發(fā)到目標(biāo)地址塑径。上面的示例是指填具,請求時(shí)間在 2019年1月1日0點(diǎn)0分0秒之后的所有請求都轉(zhuǎn)發(fā)到地址https://blog.csdn.net。+08:00是指時(shí)間和UTC時(shí)間相差八個(gè)小時(shí)誉简,時(shí)間地區(qū)為Asia/Shanghai。
添加完路由規(guī)則之后描融,訪問地址http://localhost:8080會(huì)自動(dòng)轉(zhuǎn)發(fā)到https://www.baidu.com窿克。
Before Route Predicate 剛好相反,在某個(gè)時(shí)間之前的請求的請求都進(jìn)行轉(zhuǎn)發(fā)年叮。我們把上面路由規(guī)則中的 After 改為 Before,如下:
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Before=2019-01-01T00:00:00+08:00[Asia/Shanghai]
就表示在這個(gè)時(shí)間之前可以進(jìn)行路由一姿,在這時(shí)間之后停止路由跃惫,修改完之后重啟項(xiàng)目再次訪問地址http://localhost:8080爆存,頁面會(huì)報(bào) 404 沒有找到地址。
除過在時(shí)間之前或者之后外先较,Gateway 還支持限制路由請求在某一個(gè)時(shí)間段范圍內(nèi),可以使用 Between Route Predicate 來實(shí)現(xiàn)曾棕。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Between=2019-01-01T00:00:00+08:00[Asia/Shanghai],2019-07-01T00:00:00+08:00[Asia/Shanghai]
這樣設(shè)置就意味著在這個(gè)時(shí)間段內(nèi)可以匹配到此路由翘地,超過這個(gè)時(shí)間段范圍則不會(huì)進(jìn)行匹配债朵。通過時(shí)間匹配路由的功能很酷,可以用在限時(shí)活動(dòng)的一些場景中臭杰。
4.3 通過 Cookie 匹配
Cookie Route Predicate 可以接收兩個(gè)參數(shù)谚中,一個(gè)是 Cookie name ,一個(gè)是正則表達(dá)式寥枝,路由規(guī)則會(huì)通過獲取對應(yīng)的 Cookie name 值和正則表達(dá)式去匹配磁奖,如果匹配上就會(huì)執(zhí)行路由,如果沒有匹配上則不執(zhí)行冠跷。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Cookie=sessionId,test
使用 curl 測試身诺,命令行輸入:
curl http://localhost:8080 --cookie "sessionId=test"
則會(huì)返回頁面代碼,如果去掉–cookie "sessionId=test"橄务,后臺匯報(bào) 404 錯(cuò)誤穴亏。
4.4 通過 Header 屬性匹配
Header Route Predicate 和 Cookie Route Predicate 一樣,也是接收 2 個(gè)參數(shù)棠涮,一個(gè) header 中屬性名稱和一個(gè)正則表達(dá)式蟆湖,這個(gè)屬性值和正則表達(dá)式匹配則執(zhí)行。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Header=X-Request-Id,\d+
使用 curl 測試,命令行輸入:
curl http://localhost:8080? -H "X-Request-Id:88"?
則返回頁面代碼證明匹配成功伦仍。將參數(shù)-H "X-Request-Id:88"改為-H "X-Request-Id:spring"再次執(zhí)行時(shí)返回404證明沒有匹配很洋。
4.5 通過 Host 匹配
Host Route Predicate 接收一組參數(shù),一組匹配的域名列表谓苟,這個(gè)模板是一個(gè) ant 分隔的模板协怒,用.號作為分隔符。它通過參數(shù)中的主機(jī)地址作為匹配規(guī)則仑撞。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Host=**.baidu.com
使用 curl 測試,命令行輸入:
curl http://localhost:8080? -H "Host: www.baidu.com"
curl http://localhost:8080? -H "Host: md.baidu.com"?
經(jīng)測試以上兩種 host 均可匹配到 host_route 路由桶良,去掉 host 參數(shù)則會(huì)報(bào) 404 錯(cuò)誤沮翔。
4.6 通過請求方式匹配
可以通過是 POST、GET歧譬、PUT搏存、DELETE 等不同的請求方式來進(jìn)行路由。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Method=GET
使用 curl 測試缩焦,命令行輸入:
# curl 默認(rèn)是以 GET 的方式去請求curl http://localhost:8080
測試返回頁面代碼责静,證明匹配到路由灾螃,我們再以 POST 的方式請求測試。
# curl 默認(rèn)是以 GET 的方式去請求curl -X POST http://localhost:8080
返回 404 沒有找到腰鬼,證明沒有匹配上路由
4.7 通過請求路徑匹配
Path Route Predicate 接收一個(gè)匹配路徑的參數(shù)來判斷是否走路由熄赡。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:http://ityouknow.com? ? ? ? ? order:0? ? ? ? ? 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)過測試第一和第二條命令可以正常獲取到頁面返回值缺虐,最后一個(gè)命令報(bào)404赏参,證明路由是通過指定路由來匹配沿盅。
4.8 通過請求參數(shù)匹配
Query Route Predicate 支持傳入兩個(gè)參數(shù)纫溃,一個(gè)是屬性名一個(gè)為屬性值,屬性值可以是正則表達(dá)式窖铡。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Query=smile
這樣配置坊谁,只要請求中包含 smile 屬性的參數(shù)即可匹配路由口芍。
使用 curl 測試,命令行輸入:
curl localhost:8080?smile=x&id=2
經(jīng)過測試發(fā)現(xiàn)只要請求匯總帶有 smile 參數(shù)即會(huì)匹配路由鬓椭,不帶 smile 參數(shù)則不會(huì)匹配小染。
還可以將 Query 的值以鍵值對的方式進(jìn)行配置,這樣在請求過來時(shí)會(huì)對屬性值和正則進(jìn)行匹配裤翩,匹配上才會(huì)走路由。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -Query=keep,pu.
這樣只要當(dāng)請求中包含 keep 屬性并且參數(shù)值是以 pu 開頭的長度為三位的字符串才會(huì)進(jìn)行匹配和路由踊赠。
使用 curl 測試呵扛,命令行輸入:
curl localhost:8080?keep=pub
測試可以返回頁面代碼,將 keep 的屬性值改為 pubx 再次訪問就會(huì)報(bào) 404,證明路由需要匹配正則表達(dá)式才會(huì)進(jìn)行路由臼疫。
4.9 通過請求 ip 地址進(jìn)行匹配
Predicate 也支持通過設(shè)置某個(gè) ip 區(qū)間號段的請求才會(huì)路由择份,RemoteAddr Route Predicate 接受 cidr 符號(IPv4 或 IPv6 )字符串的列表(最小大小為1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址烫堤,16 是子網(wǎng)掩碼)。
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? predicates:? ? ? ? ? ? -RemoteAddr=192.168.1.1/24
可以將此地址設(shè)置為本機(jī)的 ip 地址進(jìn)行測試凤价。
curl localhost:8080
如果請求的遠(yuǎn)程地址是 192.168.1.10鸽斟,則此路由將匹配。
4.10 組合使用
server:? port:8080spring:? application:? ? name:api-gateway? cloud:? ? gateway:? ? ? routes:? ? ? ? - id:gateway-service? ? ? ? ? uri:https://www.baidu.com? ? ? ? ? order:0? ? ? ? ? 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í)富蓄,請求必須同時(shí)滿足所有的條件才被這個(gè)路由匹配。
一個(gè)請求滿足多個(gè)路由的謂詞條件時(shí)慢逾,請求只會(huì)被首個(gè)成功匹配的路由轉(zhuǎn)發(fā)
以上就是今天的內(nèi)容立倍,我們主要聊了Gateway的基礎(chǔ)路由規(guī)則灭红,后面的章節(jié)我們接著聊更多的高級內(nèi)容,比如:Filter口注、熔斷和限流等变擒。