網(wǎng)關(guān)很重要,學(xué)一學(xué)Gateway
介紹服務(wù)網(wǎng)關(guān)
要認(rèn)識一樣?xùn)|西,最好的方法是從為什么需要他開始說起。
按照現(xiàn)在主流使用微服務(wù)架構(gòu)的特點(diǎn),假設(shè)現(xiàn)在有A、B、C三個服務(wù),假如這三個服務(wù)都需要做一些請求過濾和權(quán)限校驗(yàn),請問怎么實(shí)現(xiàn)?
- 每個服務(wù)自己實(shí)現(xiàn)一遍。
- 寫在一個公共的服務(wù),然后讓A、B、C服務(wù)引入公共服務(wù)的Maven依賴。
- 使用服務(wù)網(wǎng)關(guān),所有客戶端請求服務(wù)網(wǎng)關(guān)進(jìn)行請求過濾和權(quán)限校驗(yàn),然后再路由轉(zhuǎn)發(fā)到A、B、C服務(wù)。
第一種方式顯然是逆天的,這里不做討論。第二種方法稍微聰明點(diǎn),但是如果公共服務(wù)的邏輯發(fā)生改變,那么所有依賴公共服務(wù)的服務(wù)都需要重新打包部署才能生效。
所以顯而易見,使用服務(wù)網(wǎng)關(guān)則解決了以上的問題,其他服務(wù)不需要加入什么依賴,只需要在網(wǎng)關(guān)配置一些參數(shù),然后就能路由轉(zhuǎn)發(fā)到對應(yīng)的后端服務(wù),如果需要請求過濾和權(quán)限檢驗(yàn)的話,都可以在網(wǎng)關(guān)層實(shí)現(xiàn),如果需要更新權(quán)限校驗(yàn)的邏輯,只需要網(wǎng)關(guān)層修改就可以,其他后端服務(wù)不需要修改。
接下來再介紹一下服務(wù)網(wǎng)關(guān)的功能,主要有:
- 路由轉(zhuǎn)發(fā)
- API監(jiān)控
- 權(quán)限控制
- 限流
所以服務(wù)網(wǎng)關(guān)很重要!那么接下來我們就以目前比較主流的GateWay進(jìn)行學(xué)習(xí)吧。
GateWay入門首先第一步需要創(chuàng)建一個作為網(wǎng)關(guān)的項(xiàng)目,這里使用的SpringBoot版本是2.0.1,引入依賴:
<dependencies>
????<dependency>
????????<groupId>org.springframework.cloudgroupId>
????????<artifactId>spring-cloud-starter-gatewayartifactId>
????dependency>
dependencies>
<dependencyManagement>
????<dependencies>
????????<dependency>
????????????<groupId>org.springframework.cloudgroupId>
????????????<artifactId>spring-cloud-dependenciesartifactId>
????????????<version>Finchley.SR1version>
????????????<type>pomtype>
????????????<scope>importscope>
????????dependency>
????dependencies>
dependencyManagement>
我們需要使用網(wǎng)關(guān)轉(zhuǎn)發(fā)請求,那么首先需要有個后端服務(wù),這里我簡單地創(chuàng)建了一個user項(xiàng)目。然后啟動user項(xiàng)目,寫個獲取所有用戶信息的接口:

那么我們現(xiàn)在配置網(wǎng)關(guān)的application.yml實(shí)現(xiàn)請求轉(zhuǎn)發(fā)。
server:
??port:?9201
spring:
??application:
????name:?api-gateway
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList?#路由的ID
??????????uri:?http://localhost:8080/user/getList?#最終目標(biāo)的請求地址
??????????predicates:?#斷言
????????????-?Path=/user/getList?#路徑相匹配的進(jìn)行路由
也就是說我請求http://localhost:9201/user/getList后,9201端口是網(wǎng)關(guān)服務(wù),會匹配/user/getList的路由,最終轉(zhuǎn)發(fā)到目標(biāo)地址http://localhost:8080/user/getList。

這就算是gateway網(wǎng)關(guān)的簡單使用了。
繼續(xù)深入在上面入門的例子中,我們注意到有個predicates的配置,有點(diǎn)對其似懂非懂的感覺。中文翻譯過來叫做斷言,有點(diǎn)類似于Java8的Stream流里的Predicate函數(shù)的意思。如果斷言是真的,則匹配路由。
除此之外,gateway的另一個核心是Filter(過濾器),F(xiàn)ilter有全局和局部兩種。那么整個gateway的流程是怎么樣的呢?請看下圖:

從圖中可以看出,gateway的兩大核心就是斷言(Predicate)和過濾(Filter),接下來我們重點(diǎn)講講這兩者的使用。
Route Predicate 的使用Spring Cloud Gateway包括許多內(nèi)置的Route Predicate工廠,所以可以直接通過配置直接使用各種內(nèi)置的Predicate。
After Route Predicate
在指定的時間之后請求匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?After=2021-10-30T01:00:00+08:00[Asia/Shanghai]
Before Route Predicate
在指定時間之前的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?Before=2021-10-30T02:00:00+08:00[Asia/Shanghai]
Between Route Predicate
在指定時間區(qū)間內(nèi)的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
???????????-?Between=2021-10-30T01:00:00+08:00[Asia/Shanghai],2021-10-30T02:00:00+08:00[Asia/Shanghai]
Cookie Route Predicate
帶有指定Cookie的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
???????????-?Cookie=username,yehongzhi
使用POSTMAN發(fā)送帶有Cookie里username=yehongzhi的請求。

Header Route Predicate
帶有指定請求頭的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
?????????-?Header=X-Id,?\d+
使用POSTMAN發(fā)送請求頭帶有X-Id的請求。

Host Route Predicate
帶有指定Host的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?Host=**.yehongzhi.com
使用POSTMAN發(fā)送請求頭帶有Host=www.yehongzhi.com的請求。

Path Route Predicate
發(fā)送指定路徑的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?Path=/user/getList
直接在瀏覽器輸入該地址http://localhost:9201/user/getList,即可訪問。
Method Route Predicate
發(fā)送指定方法的請求會匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_getList
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?Method=POST
用POSTMAN以POST方式發(fā)送請求。

Query Route Predicate
帶指定查詢參數(shù)的請求可以匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_query_byName
??????????uri:?http://localhost:8080/user/query/byName
??????????predicates:
????????????-?Query=name
在瀏覽器輸入http://localhost:9201/user/query/byName?name=tom地址,發(fā)送請求。

Weight Route Predicate
使用權(quán)重來路由相應(yīng)請求,以下配置表示有80%的請求會被路由到localhost:8080,20%的請求會被路由到localhost:8081。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_1
??????????uri:?http://localhost:8080
??????????predicates:
????????????-?Weight=group1,?8
????????-?id:?user_2
??????????uri:?http://localhost:8081
??????????predicates:
????????????-?Weight=group1,?2
RemoteAddr Route Predicate
從指定的遠(yuǎn)程地址發(fā)起的請求可以匹配該路由。
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_1
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?RemoteAddr=192.168.1.4
使用瀏覽器請求。

組合使用
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_1
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?RemoteAddr=192.168.1.4
????????????-?Method=POST
????????????-?Cookie=username,yehongzhi
????????????-?Path=/user/getList
使用POSTMAN發(fā)起請求,使用POST方式,uri是/user/getList,帶有Cookie,RemoteAddr。
自定義Predicate如果我們需要自定義Predicate,怎么玩呢?其實(shí)很簡單,看源碼,有樣學(xué)樣,需要繼承AbstractRoutePredicateFactory類。
下面舉個例子,需求是token值為abc的則匹配路由,怎么寫呢,請看代碼:
@Component
public?class?TokenRoutePredicateFactory?extends?AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config>?{
????public?static?final?String?TOKEN_KEY?=?"tokenValue";
????public?TokenRoutePredicateFactory()?{
????????//當(dāng)前類的Config類,會利用反射創(chuàng)建Config并賦值,在apply傳回來
????????super(TokenRoutePredicateFactory.Config.class);
????}
????@Override
????public?List?shortcutFieldOrder()? {
????????//"tokenValue"跟Config的接收字段一致
????????return?Arrays.asList(TOKEN_KEY);
????}
????@Override
????public?Predicate?apply(Config?config)? {
????????//這里獲取的config對象就是下面自定義的Config對象
????????return?new?Predicate()?{
????????????@Override
????????????public?boolean?test(ServerWebExchange?exchange)?{
????????????????MultiValueMap?params?=?exchange.getRequest().getQueryParams();
????????????????//獲取請求參數(shù)
????????????????String?value?=?params.getFirst("token");
????????????????//請求參數(shù)和配置文件定義的token進(jìn)行對比,相等則返回true
????????????????return?config.getTokenValue()?!=?null?&&?config.getTokenValue().equals(value);
????????????}
????????};
????}
?//用來接收配置文件定義的值
????public?static?class?Config?{
????????private?String?tokenValue;
????????public?String?getTokenValue()?{
????????????return?tokenValue;
????????}
????????public?void?setTokenValue(String?tokenValue)?{
????????????this.tokenValue?=?tokenValue;
????????}
????}
}
這里需要注意的一點(diǎn)是類名必須是RoutePredicateFactory結(jié)尾,前面的則作為配置名。比如TokenRoutePredicateFactory的配置名則為Token,這是一個約定的配置。
接著在配置文件中加上該配置:
spring:
??cloud:
????gateway:
??????routes:
????????-?id:?user_1
??????????uri:?http://localhost:8080/user/getList
??????????predicates:
????????????-?Token=abc?##使用TokenRoutePredicateFactory進(jìn)行斷言
然后用POSTMAN發(fā)送請求,帶上token參數(shù),參數(shù)值為abc。

如果token的值不正確的話,會報404。

整合注冊中心
為什么要整合注冊中心呢?因?yàn)槊總€服務(wù)一般背后都不只一臺機(jī)器,而且一般使用服務(wù)名進(jìn)行配置,而不是配置服務(wù)的IP地址,并且要實(shí)現(xiàn)負(fù)載均衡調(diào)用。
這里我就使用Nacos作為注冊中心。
引入Maven依賴:
<dependency>
????<groupId>org.springframework.cloudgroupId>
????<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependencyManagement>
????<dependencies>
????????<dependency>
????????????<groupId>org.springframework.cloudgroupId>
????????????<artifactId>spring-cloud-dependenciesartifactId>
????????????<version>Finchley.SR1version>
????????????<type>pomtype>
????????????<scope>importscope>
????????dependency>
????????<dependency>
????????????<groupId>org.springframework.cloudgroupId>
????????????<artifactId>spring-cloud-alibaba-dependenciesartifactId>
????????????<version>0.2.2.RELEASEversion>
????????????<type>pomtype>
????????????<scope>importscope>
????????dependency>
????dependencies>
dependencyManagement>
啟動類加上注解,開啟注冊中心。
@SpringBootApplication
@EnableDiscoveryClient
public?class?GatewayApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(GatewayApplication.class,?args);
????}
}
在application.yml加上配置:
spring:
??application:
????name:?api-gateway
??cloud:
????nacos:
??????discovery:
????????server-addr:?127.0.0.1:8848
????????service:?${spring.application.name}
????gateway:
??????routes:
????????-?id:?consumer
??????????uri:?lb://consumer?#使用lb協(xié)議,consumer是服務(wù)名,不再使用IP地址配置
??????????order:?1
??????????predicates:
????????????-?Path=/consumer/**?#匹配/consumer/**的請求路徑
server:
??port:?9201
創(chuàng)建一個consumer也注冊到nacos,并提供一個接口:
@RestController
public?class?ConsumerController?{
????@Value("${server.port}")
????private?String?port;
????
????@RequestMapping("consumer/getDetail/{id}")
????public?String?getDetail(@PathVariable("id")?String?id)?{
????????return?"端口號:"?+?port?+?",獲取ID為:"?+?id?+?"的商品詳情";
????}
}
啟動consumer和gateway兩個項(xiàng)目,然后打開nacos控制臺,可以看到兩個服務(wù)。

連續(xù)請求地址http://localhost:9201/consumer/getDetail/1,可以看到實(shí)現(xiàn)了負(fù)載均衡調(diào)用服務(wù)。


可能有人會覺得每個服務(wù)都要配一個路由,很麻煩。有個很簡單的配置可以解決這個問題:
spring:
????gateway:
??????discovery:
????????locator:
??????????enabled:?true
然后啟動服務(wù),再試一次,請求地址需要加上服務(wù)名,依然沒有問題!
寫在最后這篇文章主要介紹GateWay的路由轉(zhuǎn)發(fā)功能,并且整合了注冊中心。權(quán)限控制可以用過濾器實(shí)現(xiàn),由于篇幅有點(diǎn)長,過濾器放到下一篇文章了,感謝大家的閱讀。
覺得有用就點(diǎn)個贊吧,你的點(diǎn)贊是我創(chuàng)作的最大動力~
我是一個努力讓大家記住的程序員。我們下期再見?。?!
能力有限,如果有什么錯誤或者不當(dāng)之處,請大家批評指正,一起學(xué)習(xí)交流!
