Gateway閘道器
學習地址:https://www.bilibili.com/video/BV18E411x7eT?p=65
Gateway
gateway 官方地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
-
SpringCloudGateway是SpringCloud的一個全新專案,基於Spring5.0+SpringBoot2.0和ProjectReactor等技術開發的閘道器,它旨在為微服務架構提供一種簡單有效的統一的API路由管理方式。
-
Spring Cloud Gateway作為Spring Cloud生態系統中的閘道器,目標是替代Zuul,在SpringCloud2.0以上版本中,沒有對新版本的Zuul2.0以上最新高效能版本進行整合,仍然還是使用的Zuul 1.x非Reactor模式的老版本。而為了提升閘道器的效能,Spring Cloud Gateway是基於WebFlux框架實現的,而webFlu×框架底層則使用了高效能的Reactor模式通訊框架Netty。
Spring Cloud Gateway的目標提供統一的路由方式且基於Filter鏈的方式提供了閘道器基本的功能,例如:安全,監控/指標和限流。 -
Spring Cloud Gateway 使用的Webflux中的reactor-netty響應式程式設計元件,底層使用了Netty通訊框架
-
用途:反向代理、鑑權、流量控制、熔斷、日誌監控
-
Spring Cloud Gateway特性
-
基於Spring Framework 5,Project Rekctor和Spring Boot2.0進形構建
-
動態路由:能夠匹配任何請求屬性;
-
可以對路由指定Predicate(斷言)和Filter(過濾器)·
-
整合Hystrix的斷路器功能;
-
整合Spring Cloud服務發現功能;
-
易於編寫的Predicate(斷言)和Filter(過濾器)。
-
請求限流功能;
-
支援路徑重寫。
-
WebFlux
-
傳統的Web框架比如說:struts2,springmvc等都是基於ServletAPI與Servlet容器基礎之上執行的。
-
在Servlet3.1之後有了非同步非阻塞的支援,而WebFlux是一個典型非阻塞非同步的框架,它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來說,它可以執行在諸如Netty,Undertow及支援Servlet3.1的容器上。非阻塞式+函數語言程式設計(Spring5必須讓你使用java8)
-
Spring WebFlux是Spring5.0引入的新的響應式框架,區別於SpringMVC,它不需要依賴Servlet API,它是完全非同步非阻塞的,並且基於Reactor來實現響應式流規範。
Zuul
zuul 1.x 官方地址:https://github.com/Netflix/zuul/wiki
-
Spring Cloud中所整合的Zuul版本,採用的是Tomcat容器使用的是傳統的servlet IO處理模型
-
Servlet生命週期,servlet由servlet container進行生命週期管理。
-
container啟動時構造servlet物件並呼叫servlet init()進行初始化;
-
container執行時接受請求,併為每個請求分配一個執行緒(一般從執行緒池中獲取空閒執行緒)然後呼叫service()。
-
container關閉時呼叫servlet destory()銷燬servlet
-
-
上述模式的缺點:
-
servlet是一個簡單的網IO模型,當請求進入servletcontainer時,servlet container就會為且繫結一個執行緒,在併發不高的場景下這種模型是適用的。但是一旦高併發(比如抽風用jemeter壓力測試),執行緒數量就會上漲,而執行緒資源代價是昂貴的(上線文切換,記憶體消耗大)嚴重影響請求的處理時間。
-
在一些簡單業務場景下,不希望為每個request分配一個執行緒,只需要1個或幾個執行緒就能應對極大併發的請求,這種業務場景下servlet模型沒有優勢
-
所以Zuul1.x是基於servlet之上的一個的阻塞式處理模型,即spring實現了處理所有request請求的一個servlet(DispatcherServlet)並由該servlet阻塞式處理處理。所以Springclou Zuul無法擺脫servlet模型的弊端
-
SpringCloudGateway與Zuul的區別
在Spring Cloud Finchley正式版之前,Spring Cloud推薦的閘道器是Netflix提供的Zuul:
-
Zuul1.x,是一個基於阻塞I/O的API Gateway
-
Zuul1.x基於Servlet2.5使用阻塞架構它不支援任何長連線(如WebSocket)Zuul的設計模式和Nginx較像,每次I/O操作都是從工作執行緒中選擇一個執行,請求執行緒被阻塞到工作執行緒完成,但是差別是Nginx用C++實現,Zuul用Java實現,而JVM本身會有第
一次載入較慢的情況,使得Zuul的效能相對較差。 -
Zuul 2.x理念更先進,想基於Netty非阻塞和支援長連線,但Spring Cloud目前還沒有整合。Zuul2.x的效能較Zuul1.x有較大提升在效能方面,根據官方提供的基準測試,Spring Cloud Gateway的RPS(每秒請求數)是Zuul的1.6倍。
-
Spring Cloud Gateway建立在Spring Framework5、ProJect Reactor和Spring Boot2之上,使用阻塞API。
-
Spring Cloud Gateway還支援WebSocket,並且與Spring緊密整合擁有更好的開發體驗
小結:
-
Zuul1.0已經進入了維護階段,而且Gateway是SpringCloud團隊研發的,值得信賴。而且很多功能Zuul都沒有用起來也非常的筒單便捷。
-
Gateway是基於非同步非阻塞模型上進行開發的,效能方面不需要擔心。雖然Netflix早就釋出了最新的Zuul2.x,但Spring Cloud貌似沒有整合計劃。而且Netflix相關元件都宣佈進入維護期;不知前景如何?多方面綜合考慮Gateway是很理想的閘道器選擇。
三大核心概念
- Route(路由)
- 路由是構建閘道器的基本模組,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言為true則匹配該路由
- Predicate(斷言)
- 參考的是java8的java.util.function.Predicate開發人員可以匹配HTTP請求中的所有內容(例如請求頭或請求引數),如果請求與斷言相匹配則進行路由
- Filter(過濾)
- 指的是Spring框架中GatewayFilter的例項,使用過濾器,可以在請求被路由前或者之後對請求進行修改。
-
web請求,通過一些匹配條件,定位到真正的服務節點。並在這個轉發過程的前後,進行一些精細化控制。
-
predicate就是我們的匹配條件;而filter,就可以理解為一個無所不能的攔截器。有了這兩個元素,在加上目標uri,就可以實現一個具體的路由了
工作流程
-
客戶端向Spring Cloud Gateway發出請求。然後在Gateway Handler Mapping中找到與請求相匹配的路由,將其傳送到Gateway Web Handler。
-
Handler再通過指定的過濾器鏈來將請求傳送到我們實際的服務執行業務邏輯,然後返回。
過濾器之間用虛線分開是因為過濾器可能會在傳送代理請求之前("pre")或之後("post")執行業務邏輯。 -
Filter在"pre"型別的過濾器可以做引數校驗、許可權校驗、流量監控、日誌輸出、協議轉換等,在"post"型別的過濾器中可以做響應內容、響應頭的修改,日誌的輸出,流量監控等有著非常重要的作用。
-
小結:路由轉發+執行過濾器鏈
搭建
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>springcloud2020</artifactId>
<groupId>com.nuc.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>
<dependency>
<groupId>com.nuc.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</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>
</project>
- 寫YML
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8001 #匹配後提供服務的路由地址
predicates:
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
- 主啟動類
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class,args);
}
}
-
路由對映
cloud-provider-payment8001的controller的訪問地址 get / lb 檢視 yml 配置
-
測試
啟動7001
啟動8001
啟動9527閘道器
http://localhost:8001/payment/get/31
http://localhost:9527/payment/get/31
Gateway閘道器路由有兩種配置方式
-
在配置檔案yml中配置
-
程式碼中注入RouteLocator的Bean
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_rote_nuc", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
通過微服務名實現動態路由
預設情況下Gateway會根據註冊中心的服務列表,以註冊中心上微服務名為路徑建立動態路由進行轉發,從而實現動態路由的功能
-
一個eureka7001+兩個服務提供者8001/8002
-
改YML
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從註冊中心動態建立路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配後提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配後提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
需要注意的是uri的協議為lb,表示啟用Gateway的負載均衡功能。lb://serviceName是spring cloud gateway在微服務中自動為我們建立的負載均衡uri
-
測試
一個eureka7001+兩個服務提供者8001/8002
http://localhost:9527/payment/lb
Predicate的使用
-
Spring Cloud Gateway將路由匹配作為Spring WebFIux Handler Mapping基礎架構的一部分
-
Spring Cloud Gateway包括許多內建的Route Predicate工廠。所有這些Predicate都與HTTP請求的不同屬性匹配。多個Route Predicate工廠可以進行組合
-
Spring Cloud Gateway建立Route物件時,使用RoutePredicateFactory建立Predicate物件,Predicate物件可以賦值給Route。Spring Cloud Gateway包含許多內建的Route Predicate Factories。
-
所有這些渭詞都匹配HTTP請求的不同屬性。多種謂詞工廠可以組合,並通過邏輯and。
常用的Route Predicate
- After Route Predicate
// 獲取時間
public class Test {
public static void main(String[] args) {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
}
}
//2020-10-13T15:47:52.943+08:00[Asia/Shanghai]
- After=2020-10-13T15:47:52.943+08:00[Asia/Shanghai]
- Before Route Predicate
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Between Route Predicate
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
-
Cookie Route Predicate
需要兩個引數,一個是Cookie name , 一個是正則表示式
路由規則會通過獲取對應Cookie name 值和正則表示式去匹配,如果匹配上就會執行路由,反之不執行
- Cookie=username,mobiw #並且Cookie是username=mobiw才能訪問
-
Header Route Predicate
兩個引數,一個是屬性名稱 , 一個是正則表示式,這個屬性值和正則表示式匹配則執行
- Header=X-Request-Id, \d+ #請求頭中要有X-Request-Id屬性並且值為整數的正則表示式
- Host Route Predicate
- Host=**.mobiw.com
- Method Route Predicate
- Method=GET
- Path Route Predicate
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- Query Route Predicate
- Query=username, \d+ #要有引數名稱並且是正整數才能路由
- 小總結
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從註冊中心動態建立路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配後提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #斷言,路徑相匹配的進行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配後提供服務的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #並且Cookie是username=zhangshuai才能訪問
#- Header=X-Request-Id, \d+ #請求頭中要有X-Request-Id屬性並且值為整數的正則表示式
#- Host=**.mobiw.com
#- Method=GET
#- Query=username, \d+ #要有引數名稱並且是正整數才能路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
Filter的使用
路由過濾器可用於修改進入的HTTP請求和返回的HTTP響應,路由過濾器只能指定路由進行使用。
Spring Cloud Gateway內建了多種路由過濾器,他們都由GatewayFilter的工廠類來產生
-
生命週期,Only Two
-
pre在業務邏輯之前
-post在業務邏輯之後
-
-
種類
-
GatewayFilter單一
-
GlobalFilter全域性
-
-
常用的GatewayFilter
AddRequestParameter
filters:
# 過濾器工廠會在匹配的請求頭加上一對請求頭,名稱X-Request-Id值為1024
-AddRequestParameter=X-Request-Id,1024
自定義過濾器
自定義全域性GlobalFilter
impiemerts 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("username");
if (StringUtils.isEmpty("username")) {
log.info("*****使用者名稱為Null 非法使用者,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//給人家一個迴應
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
測試
http://localhost:9527/payment/lb?uname=z3