文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary
介紹服務(wù)網(wǎng)關(guān)
要認(rèn)識(shí)一樣?xùn)|西,最好的方法是從為什么需要他開始說起。
按照現(xiàn)在主流使用微服務(wù)架構(gòu)的特點(diǎn),假設(shè)現(xiàn)在有A、B、C三個(gè)服務(wù),假如這三個(gè)服務(wù)都需要做一些請(qǐng)求過濾和權(quán)限校驗(yàn),請(qǐng)問怎么實(shí)現(xiàn)?
- 每個(gè)服務(wù)自己實(shí)現(xiàn)一遍。
- 寫在一個(gè)公共的服務(wù),然后讓A、B、C服務(wù)引入公共服務(wù)的Maven依賴。
- 使用服務(wù)網(wǎng)關(guān),所有客戶端請(qǐng)求服務(wù)網(wǎng)關(guān)進(jìn)行請(qǐng)求過濾和權(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ā)到對(duì)應(yīng)的后端服務(wù),如果需要請(qǐng)求過濾和權(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入門
首先第一步需要?jiǎng)?chuàng)建一個(gè)作為網(wǎng)關(guān)的項(xiàng)目,這里使用的SpringBoot版本是2.0.1,引入依賴:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
我們需要使用網(wǎng)關(guān)轉(zhuǎn)發(fā)請(qǐng)求,那么首先需要有個(gè)后端服務(wù),這里我簡(jiǎn)單地創(chuàng)建了一個(gè)user項(xiàng)目。然后啟動(dòng)user項(xiàng)目,寫個(gè)獲取所有用戶信息的接口:

那么我們現(xiàn)在配置網(wǎng)關(guān)的Application.yml實(shí)現(xiàn)請(qǐng)求轉(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)的請(qǐng)求地址
predicates: #斷言
- Path=/user/getList #路徑相匹配的進(jìn)行路由
也就是說我請(qǐng)求http://localhost:9201/user/getList后,9201端口是網(wǎng)關(guān)服務(wù),會(huì)匹配/user/getList的路由,最終轉(zhuǎn)發(fā)到目標(biāo)地址http://localhost:8080/user/getList。

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

從圖中可以看出,gateway的兩大核心就是斷言(Predicate)和過濾(Filter),接下來我們重點(diǎn)講講這兩者的使用。
Route Predicate 的使用
Spring Cloud Gateway包括許多內(nèi)置的Route Predicate工廠,所以可以直接通過配置直接使用各種內(nèi)置的Predicate。
After Route Predicate
在指定的時(shí)間之后請(qǐng)求匹配該路由。
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
在指定時(shí)間之前的請(qǐng)求會(huì)匹配該路由。
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
在指定時(shí)間區(qū)間內(nèi)的請(qǐng)求會(huì)匹配該路由。
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的請(qǐng)求會(huì)匹配該路由。
spring:
cloud:
gateway:
routes:
- id: user_getList
uri: http://localhost:8080/user/getList
predicates:
- Cookie=username,yehongzhi
使用POSTMAN發(fā)送帶有Cookie里username=yehongzhi的請(qǐng)求。

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

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

Path Route Predicate
發(fā)送指定路徑的請(qǐng)求會(huì)匹配該路由。
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ā)送指定方法的請(qǐng)求會(huì)匹配該路由。
spring:
cloud:
gateway:
routes:
- id: user_getList
uri: http://localhost:8080/user/getList
predicates:
- Method=POST
用POSTMAN以POST方式發(fā)送請(qǐng)求。

Query Route Predicate
帶指定查詢參數(shù)的請(qǐng)求可以匹配該路由。
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ā)送請(qǐng)求。

Weight Route Predicate
使用權(quán)重來路由相應(yīng)請(qǐng)求,以下配置表示有80%的請(qǐng)求會(huì)被路由到localhost:8080,20%的請(qǐng)求會(huì)被路由到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ā)起的請(qǐng)求可以匹配該路由。
spring:
cloud:
gateway:
routes:
- id: user_1
uri: http://localhost:8080/user/getList
predicates:
- RemoteAddr=192.168.1.4
使用瀏覽器請(qǐng)求。

組合使用
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ā)起請(qǐng)求,使用POST方式,uri是/user/getList,帶有Cookie,RemoteAddr。

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

如果token的值不正確的話,會(huì)報(bào)404。

整合注冊(cè)中心
為什么要整合注冊(cè)中心呢?因?yàn)槊總€(gè)服務(wù)一般背后都不只一臺(tái)機(jī)器,而且一般使用服務(wù)名進(jìn)行配置,而不是配置服務(wù)的IP地址,并且要實(shí)現(xiàn)負(fù)載均衡調(diào)用。
這里我就使用Nacos作為注冊(cè)中心。
引入Maven依賴:
<dependency><!-- SpringCloud nacos服務(wù)發(fā)現(xiàn)的依賴 -->
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
啟動(dòng)類加上注解,開啟注冊(cè)中心。
@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/**的請(qǐng)求路徑
server:
port: 9201
創(chuàng)建一個(gè)consumer也注冊(cè)到nacos,并提供一個(gè)接口:
@RestController
public class ConsumerController {
@Value("${server.port}")
private String port;
@RequestMapping("consumer/getDetail/{id}")
public String getDetail(@PathVariable("id") String id) {
return "端口號(hào):" + port + ",獲取ID為:" + id + "的商品詳情";
}
}
啟動(dòng)consumer和gateway兩個(gè)項(xiàng)目,然后打開nacos控制臺(tái),可以看到兩個(gè)服務(wù)。

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


可能有人會(huì)覺得每個(gè)服務(wù)都要配一個(gè)路由,很麻煩。有個(gè)很簡(jiǎn)單的配置可以解決這個(gè)問題:
spring:
gateway:
discovery:
locator:
enabled: true
然后啟動(dòng)服務(wù),再試一次,請(qǐng)求地址需要加上服務(wù)名,依然沒有問題!

寫在最后
這篇文章主要介紹GateWay的路由轉(zhuǎn)發(fā)功能,并且整合了注冊(cè)中心。權(quán)限控制可以用過濾器實(shí)現(xiàn),由于篇幅有點(diǎn)長(zhǎng),過濾器放到下一篇文章了,感謝大家的閱讀。
覺得有用就點(diǎn)個(gè)贊吧,你的點(diǎn)贊是我創(chuàng)作的最大動(dòng)力~
我是一個(gè)努力讓大家記住的程序員。我們下期再見!!!