文章已收錄到我的Github精選,歡迎Star:https://github.com/yehongzhi/learningSummary
介紹服務網關
要認識一樣東西,最好的方法是從為什么需要他開始說起。
按照現在主流使用微服務架構的特點,假設現在有A、B、C三個服務,假如這三個服務都需要做一些請求過濾和權限校驗,請問怎么實現?
- 每個服務自己實現一遍。
- 寫在一個公共的服務,然后讓A、B、C服務引入公共服務的Maven依賴。
- 使用服務網關,所有客戶端請求服務網關進行請求過濾和權限校驗,然后再路由轉發到A、B、C服務。
第一種方式顯然是逆天的,這里不做討論。第二種方法稍微聰明點,但是如果公共服務的邏輯發生改變,那么所有依賴公共服務的服務都需要重新打包部署才能生效。
所以顯而易見,使用服務網關則解決了以上的問題,其他服務不需要加入什么依賴,只需要在網關配置一些參數,然后就能路由轉發到對應的后端服務,如果需要請求過濾和權限檢驗的話,都可以在網關層實現,如果需要更新權限校驗的邏輯,只需要網關層修改就可以,其他后端服務不需要修改。
接下來再介紹一下服務網關的功能,主要有:
- 路由轉發
- API監控
- 權限控制
- 限流
所以服務網關很重要!那么接下來我們就以目前比較主流的GateWay進行學習吧。
GateWay入門
首先第一步需要創建一個作為網關的項目,這里使用的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>
我們需要使用網關轉發請求,那么首先需要有個后端服務,這里我簡單地創建了一個user項目。然后啟動user項目,寫個獲取所有用戶信息的接口:

那么我們現在配置網關的Application.yml實現請求轉發。
server:
port: 9201
spring:
application:
name: api-gateway
cloud:
gateway:
routes:
- id: user_getList #路由的ID
uri: http://localhost:8080/user/getList #最終目標的請求地址
predicates: #斷言
- Path=/user/getList #路徑相匹配的進行路由
也就是說我請求http://localhost:9201/user/getList后,9201端口是網關服務,會匹配/user/getList的路由,最終轉發到目標地址http://localhost:8080/user/getList。

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

從圖中可以看出,gateway的兩大核心就是斷言(Predicate)和過濾(Filter),接下來我們重點講講這兩者的使用。
Route Predicate 的使用
Spring Cloud Gateway包括許多內置的Route Predicate工廠,所以可以直接通過配置直接使用各種內置的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
在指定時間區間內的請求會匹配該路由。
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發送帶有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發送請求頭帶有X-Id的請求。

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

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

Query Route Predicate
帶指定查詢參數的請求可以匹配該路由。
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地址,發送請求。

Weight Route Predicate
使用權重來路由相應請求,以下配置表示有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
從指定的遠程地址發起的請求可以匹配該路由。
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發起請求,使用POST方式,uri是/user/getList,帶有Cookie,RemoteAddr。

自定義Predicate
如果我們需要自定義Predicate,怎么玩呢?其實很簡單,看源碼,有樣學樣,需要繼承AbstractRoutePredicateFactory類。
下面舉個例子,需求是token值為abc的則匹配路由,怎么寫呢,請看代碼:
@Component
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {
public static final String TOKEN_KEY = "tokenValue";
public TokenRoutePredicateFactory() {
//當前類的Config類,會利用反射創建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對象就是下面自定義的Config對象
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange exchange) {
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
//獲取請求參數
String value = params.getFirst("token");
//請求參數和配置文件定義的token進行對比,相等則返回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;
}
}
}
這里需要注意的一點是類名必須是RoutePredicateFactory結尾,前面的則作為配置名。比如TokenRoutePredicateFactory的配置名則為Token,這是一個約定的配置。
接著在配置文件中加上該配置:
spring:
cloud:
gateway:
routes:
- id: user_1
uri: http://localhost:8080/user/getList
predicates:
- Token=abc ##使用TokenRoutePredicateFactory進行斷言
然后用POSTMAN發送請求,帶上token參數,參數值為abc。

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

整合注冊中心
為什么要整合注冊中心呢?因為每個服務一般背后都不只一臺機器,而且一般使用服務名進行配置,而不是配置服務的IP地址,并且要實現負載均衡調用。
這里我就使用Nacos作為注冊中心。
引入Maven依賴:
<dependency><!-- SpringCloud nacos服務發現的依賴 -->
<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>
啟動類加上注解,開啟注冊中心。
@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協議,consumer是服務名,不再使用IP地址配置
order: 1
predicates:
- Path=/consumer/** #匹配/consumer/**的請求路徑
server:
port: 9201
創建一個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兩個項目,然后打開nacos控制臺,可以看到兩個服務。

連續請求地址http://localhost:9201/consumer/getDetail/1,可以看到實現了負載均衡調用服務。


可能有人會覺得每個服務都要配一個路由,很麻煩。有個很簡單的配置可以解決這個問題:
spring:
gateway:
discovery:
locator:
enabled: true
然后啟動服務,再試一次,請求地址需要加上服務名,依然沒有問題!

寫在最后
這篇文章主要介紹GateWay的路由轉發功能,并且整合了注冊中心。權限控制可以用過濾器實現,由于篇幅有點長,過濾器放到下一篇文章了,感謝大家的閱讀。
覺得有用就點個贊吧,你的點贊是我創作的最大動力~
我是一個努力讓大家記住的程序員。我們下期再見!!!