目錄
6.1.2 application.yml(yml配置方式配置路由)
6.2.2 代碼中注入RouteLocator的Bean的方式配置路由測試
1 Gateway是什麼
Gateway是一個在Spring生態系統之上構建的API網關,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一種簡單而有效的方法來路由到API,併爲它們提供跨領域的關注點,例如:安全性,監視/指標和彈性。(官網介紹)
SpringCloud全家桶中有一個很重要的組件就是網關,在1.x版本中都是採用的Zuul網關,但在2.x版本中,Netflix對Zuul的升級工作一直跳票,最終SpringCloud自己研發了Gateway用來替代Zuul。Netflix很多組件(Eureka、Hystrix等)都進入維護階段,選擇Gateway做網關是更有保障的,畢竟是SpringCloud團隊自己開發的。
SpringCloud Gateway作爲SpringCloud生態系統中的網關,目標是替代Zuul。在SpringCloud2.0以上版本中,沒有對新版本的Zuul2.0最新高性能版本進行集成,仍然還是使用zuul1.x非Reactor模式的老版本。爲了提升網關的性能,SpringCloud Gateway是基於WebFlex框架實現的,而WebFlex框架底層使用了Reactor模式通信框架Netty。
官網Gateway的工作原理圖:
2 能幹嘛
反向代理、鑑權、流量控制、熔斷、日誌監控等等。
微服務中網關處於什麼位置?
3 Gateway特性
1 ),基於Spring 5,Spring Boot 2和Project Reactor進行構建。
2),動態路由能夠匹配任何請求屬性。
3),可以對路由指定Predicate(斷言)和Filter(過濾),易於編寫Predicate和Filter。
4),集成Hystrix的斷路器功能。
5),集成SpringCloud的服務發現功能。
6),請求限流功能。
7),支持路徑重寫。
4 Gateway與Zuul的區別
5 Gateway三大核心概念
5.1 路由Route
路由是構建網關的基本模塊,它由ID、目標URL、一系列的斷言、過濾器組成,如果斷言爲true,那麼匹配該路由。
5.2 斷言Predicate
參考Java 8 Function Predicate,開發人員可以匹配Http請求中的所有內容(例如請求頭、請求參數),如果請求與斷言相匹配則進行路由。
5.3 過濾器(Filter)
過濾器是Spring框架中GatewayFilter的實例,使用過濾器可以實現在請求被路由前或者之後對請求進行修改。
5.4 Gateway工作流程
路由轉發+執行過濾器鏈。
6 Gateway代碼實操
Gateway網關路由有兩種配置方式:yml文件配置、代碼中注入RouteLocator的Bean。
6.1 基本環境搭建
6.1.1 pom依賴
在父工程下新建一個名爲cloud-gateway-gateway9527的module,對應的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>cloud2020</artifactId>
<groupId>com.bighuan.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入自定義的api通用包,可以使用Payment支付Entity-->
<dependency>
<groupId>com.bighuan.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.bighuan.springcloud.GatewayMain9527</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
6.1.2 application.yml(yml配置方式配置路由)
application.yml文件配置如下。關於路由配置可以參考官網:id表示路由的ID,沒有固定的規則,但是一定要唯一,可以配合服務名來配置;uri表示匹配後提供服務的路由地址;predicates下的Path是爲了匹配對應的路徑。
關於註冊中心的相關配置,都在此前的博客中有相關記錄,不再贅述。
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh # payment_route 路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
predicates:
- Path=/payment/get/** # 斷言,路徑相匹配的進行斷言
- id: payment_routh2 # 路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
predicates:
- Path=/payment/lb/** # 斷言,路徑相匹配的進行斷言
eureka:
instance:
hostname: cloud-gateway-service
client: # 服務提供者的provider註冊進eureka的服務列表裏
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
6.1.3 主啓動類
主啓動類並沒有特殊的配置,比較簡單。
@SpringBootApplication
@EnableEurekaClient
public class GatewayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GatewayMain9527.class,args);
}
}
6.1.4 服務提供者
在yml文件中關於路由的配置那一塊,uri和predicates下的Path等配置都是爲了指向服務提供者。服務提供者相關的博客可參考此前的博客,爲行文方便,只粘貼出對應的controller。
@GetMapping(value = "/payment/lb")
public String getPaymentLB(){
return serverPort;
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("*****查詢結果*****:" + payment+",server.port:"+serverPort);
if (payment != null) {
return new CommonResult(200, "查詢成功,serverPort:"+serverPort, payment);
} else {
return new CommonResult(444, "沒有對應記錄,查詢ID:"+id+","+"serverPort:"+serverPort, null);
}
}
6.2 Gateway測試
6.2.1 yml方式配置路由測試
分別啓動註冊中心Eureka7001、服務提供者8001、Gateway9527,瀏覽器分別訪問:
http://127.0.0.1:9527/payment/get/31、http://127.0.0.1:9527/payment/lb
發現都成功返回了對應的數據,前者需要查詢數據庫,後者則直接返回服務提供者的端口8001。通過Gateway9527,將服務提供者隱藏起來,對外界是不可見的,通過服務網關的路由就可以訪問。
6.2.2 代碼中注入RouteLocator的Bean的方式配置路由測試
代碼配置方式可參考官網的配置,如下:
本文配置類代碼如下,特別注意的是,需要讓此類可以被主啓動類掃描到。
//代碼配置: 配置路由的第二種方式
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
return routes.route("path_route_bighuan",
r -> r.path("/guonei").uri("http://news.baidu.com/guonei"))
.build();
}
}
同樣,分別啓動註冊中心7001、服務提供者8001(只測試代碼配置路由,不需要啓動也可以)、Gateway9527,訪問http://127.0.0.1:9527/guonei,頁面跳轉到到了國內百度新聞的頁面。
6.2.3 動態路由配置
以上的測試中,只有一個服務提供者8001,如果不止一個服務提供者8001,還有服務提供者8002、8003,每個都要在配置文件中配置一次的話,那麼是不現實的。動態路由就可以解決這個問題。
默認情況下,Gateway會根據註冊中心註冊的服務列表,以註冊中心上微服務名爲路徑創建動態路由進行轉發,從而實現動態路由的功能。
動態路由的配置文件爲,application-dynamic.yml,配置如下:
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true # 開啓從註冊中心動態創建路由的功能,利用微服務名進行路由
routes:
- id: payment_routh # payment_route 路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE # 匹配後提供服務的路由地址
predicates:
- Path=/payment/get/** # 斷言,路徑相匹配的進行斷言
- id: payment_routh2 # 路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://127.0.0.1:8001 # 匹配後提供服務的路由地址
uri: lb://CLOUD-PAYMENT-SERVICE # 匹配後提供服務的路由地址
predicates:
- Path=/payment/lb/** # 斷言,路徑相匹配的進行斷言
eureka:
instance:
hostname: cloud-gateway-service
client: # 服務提供者的provider註冊進eureka的服務列表裏
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
說明:
1)spring.cloud.gateway.discovery.locator.enabled設置爲true,開啓從註冊中心動態創建路由的功能,利用微服務名進行路由。
2)uri的協議爲lb,表示基於服務註冊的負載均衡,即啓動Gateway的負載均衡功能,CLOUD-PAYMENT-SERVICE是服務提供者的微服務名。
測試步驟:啓動一個註冊中心Eureka7001、分別啓動服務提供者8001和8002、啓動Gateway9527,然後連續訪問:http://127.0.0.1:9527/payment/lb,返回結果則爲8001、8002交替出現,說明動態路由配置成功,而且負載均衡也實現了。
6.2.4 斷言Predicate
SpringCloud Gateway包含許多內置的Route Predicate工廠,所有這些Predicate都與HTTP請求的不同屬性相匹配,多個Predicate工廠可以進行組合。可以理解爲Predicate就是 Sql語句中Where後的條件,可以有多個條件進行條件限制。
SpringCloud Gateway創建Route對象時,使用RoutePredicateFactory創建Predicate對象,Predicate對象可以賦值給Route對象。官網可以看到,至少內置了11中Route Predicate Factory。其中,第8種Path Route Predicate Factory前文已經接觸過了,主要用來匹配路徑。
在application-dynamic.yml的payment_routh2的predicates下增加after配置,表示需要在該時間之後才能成功匹配路由匹配。
- After=2020-03-28T17:25:10.065+08:00[Asia/Shanghai]
這種特殊的時間可以通過ZoneDateTime來獲取
ZonedDateTime now = ZonedDateTime.now();
// 2020-03-28T16:51:32.977+08:00[Asia/Shanghai]
System.out.println(now);
分別啓動項目,在設置的after時間之前訪問,會報錯。
當時間過了after時間之後,就可以正常訪問了。
Before、Between Route Predicate Factory的配置方式類似。
還是在application-dynamic.yml的payment_routh2的predicates下增加cookie配置。
- Cookie=username,bighuan
分別啓動項目,一開始請求時不帶cookie,直接返回404;帶上cookie後,訪問正常。
6.2.5 Route Filter路由過濾器
路由過濾器允許以某種方式修改傳入的HTTP請求或傳出的HTTP響應。路由過濾器適用於特定路由。Spring Cloud Gateway包括許多內置的GatewayFilter工廠。Spring Cloud Gateway內置了多種路由過濾器,它們都GatewayFilter工廠類產生。(官網gatewayfilter-factories,配置參考官網即可)
Filter的生命週期有兩種:pre、post。
在“pre”類型的過濾器可以做參數校驗、權限校驗、流量監控、日誌輸出、協議轉換等,在“post”類型的過濾器中可以做響應內容、響應頭的修改,日誌輸出,流量監控等。(注:這段話來自某大佬博客)
Filter的種類有兩種:GatewayFilter、GlobalFilter。
自定義全局GlobalFilter過濾器
自定義全局過濾器,需要實現GlobalFilter、Ordered兩個接口。自定義全局過濾器,可以實現全局日誌記錄、統一網關鑑權等等。
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("******come in MyLogGateWayFilter:"+new Date());
String uname=exchange.getRequest().getQueryParams().getFirst("uname");
if(uname == null){
log.info("*****用戶名爲空,是非法用戶,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加載過濾器的優先級,值越小,優先級越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
測試:使用application.yml啓動Gateway9527,訪問http://127.0.0.1:9527/payment/lb?uname=bighuan可以通過獲得結果,http://127.0.0.1:9527/payment/lb則會被攔截。
7 總結
雖然寫這篇博客花的的時間比較長,但堅持堅持,會有收穫的!