一萬了解 Gateway 知識點
1.什么是網(wǎng)關(guān)
API 網(wǎng)關(guān)是一個搭建在客戶端和微服務之間的服務,我們可以在 API 網(wǎng)關(guān)中處理一些非業(yè)務功能的邏輯,例如權(quán)限驗證、監(jiān)控、緩存、請求路由等。
網(wǎng)關(guān)的核心作用就是路由轉(zhuǎn)發(fā) API 網(wǎng)關(guān)就像整個微服務系統(tǒng)的門面一樣,是系統(tǒng)對外的唯一入口。有了它,客戶端會先將請求發(fā)送到 API 網(wǎng)關(guān),然后由 API 網(wǎng)關(guān)根據(jù)請求的標識信息將請求轉(zhuǎn)發(fā)到微服務實例。如下圖:

2. 為什么要網(wǎng)關(guān)
在微服務架構(gòu)中,一個系統(tǒng)往往由多個微服務組成,而這些服務可能部署在不同機房、不同地區(qū)、不同域名下。這種情況下,客戶端(例如瀏覽器、手機、軟件工具等)想要直接請求這些服務,就需要知道它們具體的地址信息,例如 IP 地址、端口號等。這種客戶端直接請求服務的方式存在以下問題:
●當服務數(shù)量眾多時,客戶端需要維護大量的服務地址,這對于客戶端來說,是非常繁瑣復雜的。
●在某些場景下可能會存在跨域請求的問題。
●身份認證的難度大,每個微服務需要獨立認證。
我們可以通過 API 網(wǎng)關(guān)來解決這些問題
3. 網(wǎng)關(guān)的優(yōu)點
對于服務數(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 管理功能。
4. 網(wǎng)關(guān)的種類
1Spring Cloud Gateway
2Spring Cloud Netflix Zuul
3Kong
4Nginx+Lua
5Traefik
5. Spring Cloud Gateway
5.1 什么是Spring Cloud Gateway
Spring Cloud Gateway 是 Spring Cloud 團隊基于 Spring 5.0、Spring Boot 2.0 和 Project Reactor 等技術(shù)開發(fā)的高性能 API 網(wǎng)關(guān)組件。Spring Cloud Gateway 旨在提供一種簡單而有效的途徑來發(fā)送 API,并為它們提供橫切關(guān)注點,例如:安全性,監(jiān)控/指標和彈性。
Spring Cloud Gateway 是基于 WebFlux響應式編程框架實現(xiàn)的,而 WebFlux 框架底層則使用了高性能的 Reactor 模式通信框架 Netty。
5.2 Spring Cloud Gateway的核心概念
| 核心概念 | 描述 |
|---|---|
| Route(路由) | 網(wǎng)關(guān)最基本的模塊。它由一個 ID、一個目標 URI、一組斷言(Predicate)和一組過濾器(Filter)組成。 |
| Predicate(斷言) | 路由轉(zhuǎn)發(fā)的判斷條件,我們可以通過 Predicate 對 HTTP 請求進行匹配,例如請求方式、請求路徑、請求頭、參數(shù)等,如果請求與斷言匹配成功,則將請求轉(zhuǎn)發(fā)到相應的服務。 |
| Filter(過濾器) | 過濾器,我們可以使用它對請求進行攔截和修改,還可以使用它對上文的響應進行再處理。 |
注意:其中 Route 和 Predicate 必須同時聲明。
5.3 Spring Cloud Gateway的特征
基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0 構(gòu)建。
能夠在任意請求屬性上匹配路由。
predicates(斷言) 和 filters(過濾器)是特定于路由的。
集成了 Hystrix 熔斷器。
集成了 Spring Cloud DiscoveryClient(服務發(fā)現(xiàn)客戶端)。
易于編寫斷言和過濾器。
能夠限制請求頻率。
能夠重寫請求路徑。
5.4 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)一個具體的路由了。
6. 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ā)。
常見的 Predicate 斷言如下表:
| 斷言 | 示例 | 說明 |
|---|---|---|
| Path | - Path=/dept/list/** | 當請求路徑與 /dept/list/** 匹配時,該請求才能被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
| Before | - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 11 時 47 分 34.255 秒之前的請求,才會被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
| After | - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 11 時 47 分 34.255 秒之后的請求,才會被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
| Between | - Between=2021-10-20T15:18:33.226+08:00[Asia/Shanghai],2021-10-20T15:23:33.226+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 15 時 18 分 33.226 秒 到 2021 年 10 月 20 日 15 時 23 分 33.226 秒之間的請求,才會被轉(zhuǎn)發(fā)到 http://localhost:8001 服務器上。 |
| Cookie | - Cookie=name,c.biancheng.net | 攜帶 Cookie 且 Cookie 的內(nèi)容為 name=c.biancheng.net 的請求,才會被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
| Header | - Header=X-Request-Id,\d+ | 請求頭上攜帶屬性 X-Request-Id 且屬性值為整數(shù)的請求,才會被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
| Method | - Method=GET | 只有 GET 請求才會被轉(zhuǎn)發(fā)到 http://localhost:8001 上。 |
7. 動態(tài)路由實現(xiàn)簡單的路由服務器
引入依賴
<!--特別注意:在 gateway 網(wǎng)關(guān)服務中不能引入 spring-boot-starter-web 的依賴,否則會報錯-->
<!-- Spring cloud gateway 網(wǎng)關(guān)依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
編寫配置文件
server:
port: 9527
spring:
application:
name: gateway
cloud:
gateway: #網(wǎng)關(guān)路由配置
routes:
#將service5提供的服務隱藏起來,不暴露給客戶端,只給客戶端暴露API網(wǎng)關(guān)的地址9527
- id: service5_routh #路由id,沒有固定規(guī)則,但唯一,建議與服務名對應
uri: lb://service5 #動態(tài)路由匹配后提供服務的路由地址:lb://${服務名}
predicates:
#以下是斷言條件,必選全部符合條件
- Path=/api/v1/service5/** #斷言,路徑匹配 注意:Path 中 P 為大寫
eureka:
instance:
instance-id: spring-cloud-gateway
prefer-ip-address: true
lease-expiration-duration-in-seconds: 15 #服務過期時間,如果超過這個時間沒有檢測到心跳,就將這個實例剔除
lease-renewal-interval-in-seconds: 5 #client發(fā)送心跳給server端的頻率
client:
registry-fetch-interval-seconds: 5
service-url:
defaultZone: http://eureka1001.com:1001/eureka/,http://eureka2001.com:2001/eureka/
默認情況下,Spring Cloud Gateway 會根據(jù)服務注冊中心(例如 Eureka Server)中維護的服務列表,以服務名(spring.application.name)作為路徑創(chuàng)建動態(tài)路由進行轉(zhuǎn)發(fā),從而實現(xiàn)動態(tài)路由功能。
我們可以在配置文件中,將 Route 的 uri 地址修改為以下形式:lb://service-name 以上配置說明如下:
lb(loadBalance):uri 的協(xié)議,表示開啟 Spring Cloud Gateway 的負載均衡功能。
service-name:服務名,Spring Cloud Gateway 會根據(jù)它獲取到具體的微服務地址
啟動類添加注解
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
8. Filter 過濾器
通常情況下,出于安全方面的考慮,服務端提供的服務往往都會有一定的校驗邏輯,例如用戶登錄狀態(tài)校驗、簽名校驗等。在微服務架構(gòu)中,系統(tǒng)由多個微服務組成,所有這些服務都需要這些校驗邏輯,此時我們就可以將這些校驗邏輯寫到 Spring Cloud Gateway 的 Filter 過濾器中。
Filter 的分類
Spring Cloud Gateway 提供了以下兩種類型的過濾器,可以對請求和響應進行精細化控制。
| 過濾器類型 | 說明 |
|---|---|
| Pre 類型 | 這種過濾器在請求被轉(zhuǎn)發(fā)到微服務之前可以對請求進行攔截和修改,例如參數(shù)校驗、權(quán)限校驗、流量監(jiān)控、日志輸出以及協(xié)議轉(zhuǎn)換等操作。 |
| Post 類型 | 這種過濾器在微服務對請求做出響應后可以對響應進行攔截和再處理,例如修改響應內(nèi)容或響應頭、日志輸出、流量監(jiān)控等。 |
按照作用范圍劃分,Spring Cloud gateway 的 Filter 可以分為 2 類:
●GatewayFilter:應用在單個路由或者一組路由上的過濾器。
●GlobalFilter:應用在所有的路由上的過濾器。
GatewayFilter 網(wǎng)關(guān)過濾器
GatewayFilter 是 Spring Cloud Gateway 網(wǎng)關(guān)中提供的一種應用在單個或一組路由上的過濾器。它可以對單個路由或者一組路由上傳入的請求和傳出響應進行攔截,并實現(xiàn)一些與業(yè)務無關(guān)的功能,比如登陸狀態(tài)校驗、簽名校驗、權(quán)限校驗、日志輸出、流量監(jiān)控等。
GatewayFilter 在配置文件(例如 application.yml)中的寫法與 Predicate 類似,格式如下:
spring:
cloud:
gateway:
routes:
- id: xxxx
uri: xxxx
predicates:
- Path=xxxx
filters:
- AddRequestParameter=X-Request-Id,1024 #過濾器工廠會在匹配的請求頭加上一對請求頭,名稱為 X-Request-Id 值為 1024
- PrefixPath=/dept #在請求路徑前面加上 /dept
……
Spring Cloud Gateway 內(nèi)置了多達 31 種 GatewayFilter,下表中列舉了幾種常用的網(wǎng)關(guān)過濾器及其使用示例。
| 路由過濾器 | 描述 | 參數(shù) | 使用示例 |
|---|---|---|---|
| AddRequestHeader | 攔截傳入的請求,并在請求上添加一個指定的請求頭參數(shù)。 | name:需要添加的請求頭參數(shù)的 key;value:需要添加的請求頭參數(shù)的 value。 | - AddRequestHeader=my-request-header,1024 |
| AddRequestParameter | 攔截傳入的請求,并在請求上添加一個指定的請求參數(shù)。 | name:需要添加的請求參數(shù)的 key;value:需要添加的請求參數(shù)的 value。 | - AddRequestParameter=my-request-param,c.biancheng.net |
| AddResponseHeader | 攔截響應,并在響應上添加一個指定的響應頭參數(shù)。 | name:需要添加的響應頭的 key;value:需要添加的響應頭的 value。 | - AddResponseHeader=my-response-header,c.biancheng.net |
| PrefixPath | 攔截傳入的請求,并在請求路徑增加一個指定的前綴。 | prefix:需要增加的路徑前綴。 | - PrefixPath=/consumer |
| PreserveHostHeader | 轉(zhuǎn)發(fā)請求時,保持客戶端的 Host 信息不變,然后將它傳遞到提供具體服務的微服務中。 | 無 | - PreserveHostHeader |
| RemoveRequestHeader | 移除請求頭中指定的參數(shù)。 | name:需要移除的請求頭的 key。 | - RemoveRequestHeader=my-request-header |
| RemoveResponseHeader | 移除響應頭中指定的參數(shù)。 | name:需要移除的響應頭。 | - RemoveResponseHeader=my-response-header |
| RemoveRequestParameter | 移除指定的請求參數(shù)。 | name:需要移除的請求參數(shù)。 | - RemoveRequestParameter=my-request-param |
| RequestSize | 配置請求體的大小,當請求體過大時,將會返回 413 Payload Too Large。 | maxSize:請求體的大小。 | - name: RequestSize args: maxSize: 5000000 |
GlobalFilter 全局過濾器
GlobalFilter 是一種作用于所有的路由上的全局過濾器,通過它,我們可以實現(xiàn)一些統(tǒng)一化的業(yè)務功能,例如權(quán)限認證、IP 訪問限制等。當某個請求被路由匹配時,那么所有的 GlobalFilter 會和該路由自身配置的 GatewayFilter 組合成一個過濾器鏈。Spring Cloud Gateway 為我們提供了多種默認的 GlobalFilter,例如與轉(zhuǎn)發(fā)、路由、負載均衡等相關(guān)的全局過濾器。但在實際的項目開發(fā)中,通常我們都會自定義一些自己的 GlobalFilter 全局過濾器以滿足我們自身的業(yè)務需求,而很少直接使用 Spring Cloud Config 提供這些默認的 GlobalFilter。
關(guān)于默認的全局過濾器的詳細內(nèi)容,請參考 Spring Cloud 官網(wǎng)。
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters9. 全局過濾器的各種姿勢
9.1 白名單路由、獲取請求URL參數(shù)
@Component
public class GlobalFilter1 implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("通過參數(shù)過濾器");
//1.對于特定路由的放行邏輯
URI uri = exchange.getRequest().getURI();
if ("/api/v1/service8".equals(uri.getPath())) {
return chain.filter(exchange);
}
//2.獲取URL參數(shù)
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
//3.其他路由進行權(quán)限校驗
if (null == queryParams.get("p1")) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().writeWith(Flux.just(exchange.getResponse().bufferFactory()
.wrap("請?zhí)顚懻埱髤?shù)p1".getBytes())));
} else {
return chain.filter(exchange);
}
}
}
PS:
getOrder方法可以用于指定當前filter在執(zhí)行鏈路的順序,整個鏈路按照Order由小到大的順序執(zhí)行
對于白名單鏈路放行,整個filter鏈路都要編寫放行邏輯
9.2 獲取請求體參數(shù)
@Component
public class GatewayFilter2 implements GatewayFilter, Ordered {
@Override
public int getOrder() {
return Integer.MIN_VALUE + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("通過特定過濾器2");
//1.獲取POST請求體參數(shù)
Mono<Void> postBody = DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
//2.獲取請求體參數(shù)
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
//TODO 3.在這里可以對請求體參數(shù)為所欲為了
System.out.println("bodyString:" + bodyString);
//4.釋放內(nèi)存
DataBufferUtils.release(dataBuffer);
//5.以下操作為了將請求體再次封裝寫回到request里
exchange.getAttributes().put("POST_BODY", bodyString);
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return Mono.just(buffer);
});
//6.將請求體再次封裝寫回到request里,傳到下一級,否則,由于請求體已被消費,后續(xù)的服務將取不到值
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
//7.封裝request,傳給下一級
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
//8.返回
return postBody;
}
}
9.3 為某個路由或某一組路由定制專屬過濾器
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator appRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route(r -> r.path("/api/v1/service5/**")
.filters(f -> f.filter(new GatewayFilter2()))
.uri("lb://service5")
.id("service5_routh"))
.build();
}
}
9.4 處理請求響應
@Component
public class AfterGlobalFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.獲取字節(jié)工廠
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
//3.獲取響應體
final ServerHttpResponseDecorator serverHttpResponseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
//4.返回響應
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
//5.讀取字節(jié)
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
//6.釋放掉內(nèi)存
DataBufferUtils.release(dataBuffer);
//7.這段代碼我們可以自由發(fā)揮
String responseStr = new String(content, StandardCharsets.UTF_8);
super.setStatusCode(HttpStatus.valueOf(Integer.parseInt(responseStr)));
//8.返回
return bufferFactory.wrap(content);
}));
}
return super.writeWith(body);
}
};
//2.返回
System.out.println("通過響應過濾器");
return chain.filter(exchange.mutate().response(serverHttpResponseDecorator).build());
}
}
source: www.yuque.com/u27809381/ca4o9w/ggap14記得點「贊」和「在看」↓
愛你們
