一. Gateway概述
1.Gateway是什么
gateway 官網(wǎng):https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
在微服務架構(gòu)中,一個系統(tǒng)往往由多個微服務組成,而這些服務可能部署在不同機房、不同地區(qū)、不同域名下。這種情況下,客戶端(例如瀏覽器、手機、軟件工具等)想要直接請求這些服務,就需要知道它們具體的地址信息,例如 IP 地址、端口號等。
這種客戶端直接請求服務的方式存在以下問題:
- 當服務數(shù)量眾多時,客戶端需要維護大量的服務地址,這對于客戶端來說,是非常繁瑣復雜的。
- 在某些場景下可能會存在跨域請求的問題。
- 身份認證的難度大,每個微服務需要獨立認證。
2. Gateway作用
API 網(wǎng)關(guān)是一個搭建在客戶端和微服務之間的服務,我們可以在 API 網(wǎng)關(guān)中處理一些非業(yè)務功能的邏輯,例如權(quán)限驗證、監(jiān)控、緩存、請求路由等。
API 網(wǎng)關(guān)就像整個微服務系統(tǒng)的門面一樣,是系統(tǒng)對外的唯一入口。有了它,客戶端會先將請求發(fā)送到 API 網(wǎng)關(guān),然后由 API 網(wǎng)關(guān)根據(jù)請求的標識信息將請求轉(zhuǎn)發(fā)到微服務實例。
對于服務數(shù)量眾多、復雜度較高、規(guī)模比較大的系統(tǒng)來說,使用 API 網(wǎng)關(guān)具有以下好處:
- 客戶端通過 API 網(wǎng)關(guān)與微服務交互時,客戶端只需要知道 API 網(wǎng)關(guān)地址即可,而不需要維護大量的服務地址,簡化了客戶端的開發(fā)。
- 客戶端直接與 API 網(wǎng)關(guān)通信,能夠減少客戶端與各個服務的交互次數(shù)。
- 客戶端與后端的服務耦合度降低。
- 節(jié)省流量,提高性能,提升用戶體驗。
- API 網(wǎng)關(guān)還提供了安全、流控、過濾、緩存、計費以及監(jiān)控等 API 管理功能。
常見的 API 網(wǎng)關(guān)實現(xiàn)方案主要有以下 5 種:
- Spring Cloud Gateway
- Spring Cloud Netflix Zuul
- Kong
- Nginx+Lua
- Traefik
3. 微服務架構(gòu)中網(wǎng)關(guān)的位置
4. SpringCloud Gateway概念
SpringCloud Gateway 是在建立在 Spring 生態(tài)系統(tǒng)之上的 API 網(wǎng)關(guān)服務,基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式,以及提供一些強大的過濾器功能,例如:安全,監(jiān)控/指標、熔斷、限流、重試等。
Spring Cloud Gateway 是基于 WebFlux 框架實現(xiàn)的,而 WebFlux 框架底層則使用了高性能的 Reactor 模式通信框架 Netty。
5. SpringCloud Gateway IO 模型
Zuul 1.x 的 IO 模型:
SpringCloud 中所集成的 Zuul 1.x 版本,采用的是 Tomcat 容器,使用的是傳統(tǒng)的 Servlet IO 處理模型。servlet 由 servlet container 進行生命周期管理:
- container 啟動時構(gòu)造 servlet 對象并調(diào)用 servlet.init() 進行初始化;
- container 關(guān)閉時調(diào)用 servlet.destory() 銷毀 servlet;
- container 運行時接受請求,并為每個請求分配一個線程(一般從線程池中獲取空閑線程)然后調(diào)用 service()。
上述模式的缺點:servlet 是一個簡單的網(wǎng)絡 IO 模型,當請求進入 servlet container 時,servlet container 就會為其綁定一個線程,在并發(fā)不高的場景下這種模型是適用的。但是一旦高并發(fā),線程數(shù)量就會上漲,而線程資源代價是昂貴的(上線文切換,內(nèi)存消耗大)嚴重影響請求的處理時間。在一些簡單的業(yè)務場景下,不希望為每個請求分配一個線程,只需要一個或幾個線程就能應對極大并發(fā)的請求,這種業(yè)務場景下 servlet 模型沒有優(yōu)勢。
所以 Zuul 1.x 是基于 servlet 阻塞 IO 模型的 API 網(wǎng)關(guān),即 Spring 實現(xiàn)了處理所有 request 請求的一個 servlet(DispatcherServlet),并由該 servlet 阻塞式處理處理。每次 I/О 操作都是從工作線程中選擇一個執(zhí)行,請求線程被阻塞到工作線程完成。所以 Zuul 1.x 無法擺脫 servlet 模型的弊端。
雖然 Zuul 2.0 開始,使用了 Netty 非阻塞和支持長連接,并且已經(jīng)有了大規(guī)模 Zuul 2.0 集群部署的成熟案例,但是,SpringCloud 官方已經(jīng)沒有集成該版本的計劃了。
GateWay 非阻塞異步模型
傳統(tǒng)的Web框架,比如說:Struts2,SpringMVC 等都是基于 Servlet APl 與 Servlet 容器基礎(chǔ)之上運行的。
但是在 Servlet3.1 之后有了異步非阻塞的支持。而 WebFlux 是一個典型非阻塞異步的框架,它的核心是基于 Reactor 的相關(guān) API 實現(xiàn)的。Spring WebFlux 是 Spring 5.0 引入的新的響應式框架,區(qū)別于 Spring MVC,它不需要依賴 Servlet APl,它是完全異步非阻塞的,并且基于 Reactor 來實現(xiàn)響應式流規(guī)范。
SpringCloud Gateway 是基于 WebFlux 框架實現(xiàn)的,而 WebFlux 框架底層則使用了高性能的 Reactor 模式通信框架 Netty。
二.Gateway的三大核心概念
1. Route 路由
路由是構(gòu)建網(wǎng)關(guān)的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如果斷言為true則匹配該路由。
2. Predicate 斷言
路由轉(zhuǎn)發(fā)的判斷條件,我們可以通過 Predicate 對 HTTP 請求進行匹配,例如請求方式、請求路徑、請求頭、參數(shù)等,如果請求與斷言匹配成功,則將請求轉(zhuǎn)發(fā)到相應的服務
3. Filter 過濾
過濾器,我們可以使用它對請求進行攔截和修改,還可以使用它對上文的響應進行再處理。
4. 總結(jié)
- web 請求,通過一些匹配條件,定位到真正的服務節(jié)點,并在這個轉(zhuǎn)發(fā)過程的前后,進行一些精細化控制
- predicate 就是我們的匹配條件
- filter:就可以理解為一個無所不能的攔截器,有了這兩個元素,再加上目標的uri,就可以實現(xiàn)一個具體的路由了。
三.Spring Cloud Gateway工作流程
Spring Cloud Gateway 工作流程說明如下:
- 客戶端將請求發(fā)送到
Spring Cloud Gateway
上。 -
Spring Cloud Gateway
通過Gateway Handler Mapping
找到與請求相匹配的路由,將其發(fā)送給Gateway Web Handler
。 -
Gateway Web Handler
通過指定的過濾器鏈(Filter Chain
),將請求轉(zhuǎn)發(fā)到實際的服務節(jié)點中,執(zhí)行業(yè)務邏輯,返回響應結(jié)果返回給客戶端。 - 過濾器之間用虛線分開是因為過濾器可能會在轉(zhuǎn)發(fā)請求之前(pre)或之后(post)執(zhí)行業(yè)務邏輯。
- 過濾器(
Filter
)可以在請求被轉(zhuǎn)發(fā)到服務端前,對請求進行攔截和修改,例如參數(shù)校驗、權(quán)限校驗、流量監(jiān)控、日志輸出以及協(xié)議轉(zhuǎn)換等。 - 過濾器可以在響應返回客戶端之前,對響應進行攔截和再處理,例如修改響應內(nèi)容或響應頭、日志輸出、流量監(jiān)控等。
總而言之,客戶端發(fā)送到 Spring Cloud Gateway
的請求需要通過一定的匹配條件,才能定位到真正的服務節(jié)點。在將請求轉(zhuǎn)發(fā)到服務進行處理的過程前后(pre 和 post),我們還可以對請求和響應進行一些精細化控制。
Predicate 就是路由的匹配條件,而 Filter 就是對請求和響應進行精細化控制的工具。有了這兩個元素,再加上目標 URI,就可以實現(xiàn)一個具體的路由了。
核心邏輯:路由轉(zhuǎn)發(fā)+執(zhí)行過濾鏈
四.服務搭建
1. 創(chuàng)建cloud-gateway-gateway-9527 模塊
2. 寫pom
做網(wǎng)關(guān)不需要添加 web starter
<?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>cloud2022</artifactId>
<groupId>com.jm.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.jm.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基礎(chǔ)配置類-->
<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>
3. 改yml
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
4. 主啟動
package com.jm.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run(GateWayMain9527.class, args);
}
}
5. 網(wǎng)關(guān)路由映射
我們目前不想暴露8001端口,希望在8001外面套一層9527
YML新增網(wǎng)關(guān)配置
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,沒有固定規(guī)則,但要求唯一,建議配合服務名稱
uri: http://localhost:8001 #匹配后提供的服務的路由地址
predicates:
- Path=/payment/get/** # 斷言,路徑匹配相配置的進行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/**
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
6. 測試
啟動網(wǎng)關(guān)前訪問:http://localhost:8001/get/payment/31
啟動網(wǎng)關(guān)后訪問:http://localhost:9527/get/payment/31
五.路由配置的兩種方式
1. YML 配置
spring:
application:
name: cloud-gateway
cloud:
gateway: #網(wǎng)關(guān)路由配置
routes:
- id: payment_routh #路由的ID,沒有固定規(guī)則但要求唯一,建議與服務名對應
uri: http://localhost:8001 #匹配后提供服務的路由地址
predicates:
- Path=/payment/getPaymentById/** #斷言,路徑相匹配的進行路由
- Method=GET #只能時GET請求時,才能訪問
- id: payment_routh2 #路由的ID,沒有固定規(guī)則但要求唯一,建議與服務名對應
uri: http://localhost:8001 #匹配后提供服務的路由地址
predicates:
- Path=/payment/lb/** #斷言,路徑相匹配的進行路由
測試地址:http://localhost:9527/payment/get/31
2. 代碼配置
當請求路徑為 /guonei
時,轉(zhuǎn)發(fā)到 https://news.baidu.com/guonei
。
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route",
r -> r.path("/guonei")
.uri("https://news.baidu.com/guonei"));
return routes.build();
}
}
測試:http://localhost:9527/guonei
六.Gateway 動態(tài)路由
相當于給網(wǎng)關(guān)配置一個負載均衡,因為看上面的配置把8001寫死了
默認情況下Gateway會根據(jù)注冊中心注冊的服務列表,以注冊中心上微服務名為路徑創(chuàng)建動態(tài)路由進行轉(zhuǎn)發(fā),從而實現(xiàn)動態(tài)路由的功能
開啟動態(tài)路由:spring.cloud.gateway.discovery.locator.enabled:true;
在添加uri的時候,開頭是 lb://
微服務名,lb: 負載均衡協(xié)議
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從注冊中心動態(tài)創(chuàng)建路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #lb:負載均衡協(xié)議
predicates:
- Path=/payment/get/** # 斷言,路徑相匹配的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #lb:負載均衡協(xié)議
predicates:
- Path=/payment/lb/** # 斷言,路徑相匹配的進行路由
測試:
因為開啟了8001和8002兩個端口,所以網(wǎng)關(guān)負載均衡的效果是 8001/8002切換
七.Predicate 斷言的使用
Spring Cloud Gateway 通過 Predicate 斷言來實現(xiàn) Route 路由的匹配規(guī)則。簡單點說,Predicate 是路由轉(zhuǎn)發(fā)的判斷條件,請求只有滿足了 Predicate 的條件,才會被轉(zhuǎn)發(fā)到指定的服務上進行處理。
使用 Predicate 斷言需要注意以下 3 點:
- Route 路由與 Predicate 斷言的對應關(guān)系為“一對多”,一個路由可以包含多個不同斷言。
- 一個請求想要轉(zhuǎn)發(fā)到指定的路由上,就必須同時匹配路由上的所有斷言。
- 當一個請求同時滿足多個路由的斷言條件時,請求只會被首個成功匹配的路由轉(zhuǎn)發(fā)。
參考gateway官網(wǎng)的斷言的例子:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-after-route-predicate-factory
1. 常用的斷言
常用的Route Predicate
After Route Predicate
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- 匹配該斷言時間之后的 uri請求
Before Route Predicate
-
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- 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
不帶cookies訪問
帶上cookies訪問 - Cookie=username,atguigu #并且Cookie是username=atguigu 才能訪問
Cookie Route Predicate 需要兩個參數(shù),一個時Cookie name,一個是正則表達式。
路由規(guī)則會通過獲取對應的Cookie name值和正則表達式去匹配,如果匹配上就會執(zhí)行路由,如果沒有匹配上就不執(zhí)行
Header Route Predicate
兩個參數(shù):一個是屬性名稱和一個正則表達式,這個屬性值和正則表達式匹配則執(zhí)行;
Host Route Predicate
- Host=**.atguigu.com
Method Route Predicate
- Method=GET
Path Route Predicate
== Query Route Predicate==
- Query=username, \d+ #要有參數(shù)名稱并且是正整數(shù)才能路由
2.小總結(jié)(pom)
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #開啟從注冊中心動態(tài)創(chuàng)建路由的功能,利用微服務名進行路由
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #lb:負載均衡協(xié)議
predicates:
- Path=/payment/get/** # 斷言,路徑相匹配的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務名
#uri: http://localhost:8001 #匹配后提供服務的路由地址
uri: lb://cloud-payment-service #lb:負載均衡協(xié)議
#filters:
# - AddRequestParameter=X-Request-Id,1024 #過濾器工廠會在匹配的請求頭加上一對請求頭,名稱為X-Request-Id值為1024
predicates:
- Path=/payment/lb/** # 斷言,路徑相匹配的進行路由
#- After=2022-11-26T19:14:10.785+08:00[Asia/Shanghai]
#- Before=2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
#- Between=2022-11-26T19:14:10.785+08:00[Asia/Shanghai], 2022-11-26T19:22:29.119+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 請求頭要有X-Request-Id屬性并且值為整數(shù)的正則表達式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ # 要有參數(shù)名username并且值還要是整數(shù)才能路由
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務列表內(nèi)
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
八.Filter 過濾器
1. 什么是Filter
路由過濾器可用于修改進入的Http請求和返回的Http響應,路由過濾器只能過濾指定路由進行使用。
Spring Cloud Gateway 內(nèi)置了多種路由過濾器,他們都由GatewayFilter的工廠類來產(chǎn)生
官網(wǎng):https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
聲明周期 :
- pre 在業(yè)務邏輯之前
- post 在業(yè)務邏輯之后
種類:
- 單一的:GatewayFilter
- 全局的:GlobalFilter
2. 自定義過濾器
2.1 實現(xiàn)兩個接口
implements GlobalFilter
,Ordered
文章來源:http://www.zghlxwxcb.cn/news/detail-469192.html
2.2 作用
全局日志記錄
統(tǒng)一網(wǎng)關(guān)鑒權(quán)
…文章來源地址http://www.zghlxwxcb.cn/news/detail-469192.html
2.3 演示代碼
package com.jm.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@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("********用戶名為null,非法用戶,/(ㄒoㄒ)/~~");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
到了這里,關(guān)于SpringCloud - GateWay服務網(wǎng)關(guān)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!