?? 《SpringCloud入門(mén)實(shí)戰(zhàn)系列》解鎖SpringCloud主流組件入門(mén)應(yīng)用及關(guān)鍵特性。帶你了解SpringCloud主流組件,是如何一戰(zhàn)解決微服務(wù)諸多難題的。項(xiàng)目demo:源碼地址

一、Gateway是什么?
Gateway關(guān)鍵特性:路由、斷言、過(guò)濾。
Spring Cloud Gateway是 Spring Cloud 的一個(gè)全新項(xiàng)目,基于 Spring 6.0+Spring Boot 3.0和 Project Reactor 等技術(shù)開(kāi)發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡(jiǎn)單有效的統(tǒng)一的 API路由管理方式。

Spring Cloud Gateway 作為 Spring Cloud 生態(tài)系統(tǒng)中的網(wǎng)關(guān),目標(biāo)是替代Zuul。
Cloud全家桶中有個(gè)很重要的組件就是網(wǎng)關(guān),在1.x版本中都是采用的Zuul網(wǎng)關(guān),在SpringCloud Finchley 正版之前,Spring Cloud 推薦的網(wǎng)關(guān)是 Netflix 提供的Zuul,但在2.x版本中,SpringCloud最后自己研發(fā)了一個(gè)網(wǎng)關(guān)Gateway替代Zuul。

在Spring Cloud 2.0以上版本中,沒(méi)有對(duì)新版本Zuul2.0以最新高性能版本進(jìn)行集成,仍然還是使用的Zuul 1.x非Reactor模式的老版本。而為了提升網(wǎng)關(guān)的性能,SpringCloud gateway,其于WebFlux框架實(shí)現(xiàn)的,而WebFlux框架底層,則使用了高性能的Reactor模式通訊框架Nettey。
Spring Cloud Gateway的目標(biāo)提供統(tǒng)一的路由方式且基于 Filter 鏈的方式提供了網(wǎng)關(guān)基本的功能,例如: 安全,監(jiān)控/指標(biāo),和限流。
Spring WebFlux是 Spring 5.0 引入的新的響應(yīng)式框架,區(qū)別于 Spring MVC,它不需要依賴(lài)Servlet API,它是完全異步非阻塞的,并且基于 Reactor 來(lái)實(shí)現(xiàn)響應(yīng)式流規(guī)范。
Spring WebFlux官網(wǎng)信息
二、Spring Cloud Gateway 與 Zuul的區(qū)別(選型問(wèn)題)
1、Zuul 1.x,是個(gè)其于阻塞I /O的API Gateway
2、Zuul 1.x 基于Sevlet 2.5使用阻塞架構(gòu)。它不支持任何長(zhǎng)連接(如 WebSocket) Zuul 的設(shè)計(jì)模式和Nginx較像,每次 I /O操作都是從工作線程中選擇一個(gè)執(zhí)行,請(qǐng)求線程被阻塞到工作線程完成,但是差別是Nginx 用C++ 實(shí)現(xiàn),Zuul 用Java 實(shí)現(xiàn),而 JVM 本身會(huì)有第一次加載較慢的情況,便得Zuul 的性能相對(duì)較差。
3、Zuul 2.x理念更先進(jìn),想基于Netty非阻塞和支持長(zhǎng)連接,SpringCloud目前還沒(méi)有整合。Zuul 2.x的性能較 Zuul 1.x 有較大提在性能方面,根據(jù)官方提供的基準(zhǔn)測(cè)試,Spring Cloud Gateway 的 RPS (每秒請(qǐng)求數(shù)) 是Zuul的1.6倍。
4、Spring Cloud Gateway 建立在 Spring Framework 6、 Project Reactor 和 Spring Boot 3之上,使用非阻塞API。Gateway是基于異步非阻塞模型上進(jìn)行開(kāi)發(fā)的,所以性能好,雖然Netflex發(fā)布了最新的Zuul2,但SpringCloud沒(méi)有整合的計(jì)劃。
5、Spring Cloud Gateway還支持 WebSocket,并目與Spring緊密集成擁有更好的開(kāi)發(fā)體驗(yàn)。
6、通過(guò)官網(wǎng)可知:Zuul1.0已經(jīng)進(jìn)入了維護(hù)階段,而Gateway是SpringCloud團(tuán)隊(duì)研發(fā)的,值得信賴(lài)。
Netflex 公司的zuul,官網(wǎng)地址:zuul官網(wǎng)

Spring社區(qū)的Gateway,官網(wǎng)地址:Gateway官網(wǎng)。

兩者都是網(wǎng)關(guān)。Netflex 的zuul本來(lái)要升級(jí),核心人員跳槽了,另一方面zuul升級(jí)到zuul2分歧大,導(dǎo)致zuul不維護(hù)了,zuul2研發(fā)中,于是等不及了,Spring社區(qū)出現(xiàn)了新一代網(wǎng)關(guān)技術(shù)Gateway,所以我們接下來(lái)主要學(xué)習(xí)Gateway。
三、Spring Cloud Gateway特性
通過(guò)官網(wǎng)可知,Spring Cloud Gateway有如下特性:
- (3.x版本以下)基于Spring Framework5,Project Reactor和 Spring Boot 2.0進(jìn)行構(gòu)建;
(目前最高版本4.x版本)基于Spring Framework6,Project Reactor和 Spring Boot 3.0進(jìn)行構(gòu)建; - 動(dòng)態(tài)路由:能夠匹配任何請(qǐng)求屬性;
- 可以對(duì)路由指定Predicate(斷言)和Filter(過(guò)濾器);
- 集成Hystrix的斷路器功能;
- 集成Spring Cloud服務(wù)發(fā)現(xiàn)功能;
- 易于編寫(xiě)的Predicate(斷言)和Filter(過(guò)濾器);
- 請(qǐng)求限流功能;
- 支持路徑重寫(xiě);

四、Gateway三大概念與工作流程
1、重要概念(路由、斷言、過(guò)濾)
1)Route(路由):
路由是構(gòu)建網(wǎng)關(guān)的基本模塊,由ID,目標(biāo)URI,一系列的斷言和過(guò)濾器組成,如果斷言為true,則匹配該路由。
2)Predicate(斷言):
參考java8的java.util.function.Predicate開(kāi)發(fā)人員可以匹配HTTP請(qǐng)求中的所有內(nèi)容(例如請(qǐng)求頭、請(qǐng)求參數(shù)),如果請(qǐng)求與斷言相匹配則進(jìn)行路由。
3)Filter(過(guò)濾)
指的是由Spring框架中GatewayFilter實(shí)例,使用過(guò)濾器,可以在請(qǐng)求被路由之前或者之后對(duì)請(qǐng)求進(jìn)行修改。

web請(qǐng)求,通過(guò)一些匹配條件,定位到真正的服務(wù)節(jié)點(diǎn)。并在這個(gè)轉(zhuǎn)發(fā)過(guò)程的前和后,通過(guò)Filter進(jìn)行一些精細(xì)化控制和管理,predicate就是我們的匹配條件,有了這兩個(gè)元素,再加上目標(biāo)uri,就可以實(shí)現(xiàn)一個(gè)具體的路由了。
2、工作流程(核心:路由轉(zhuǎn)發(fā)+執(zhí)行過(guò)濾鏈)

從官網(wǎng)工作流程圖可知:客戶向 Spring Cloud Gateway 發(fā)出請(qǐng)求,然后在 Gateway Handler Mapping 中找到與請(qǐng)求相匹配的路由,將其發(fā)送到GatewayWeb Handler。
Handler 再通過(guò)指定的過(guò)濾器鏈來(lái)將請(qǐng)求發(fā)這到我們實(shí)際的服務(wù)執(zhí)行業(yè)務(wù)邏輯,然后返回。
過(guò)濾器之間用虛線分開(kāi)是因?yàn)檫^(guò)濾器可能會(huì)在發(fā)送代理請(qǐng)求之前 (“pre”) 或之后(“post”) 執(zhí)行業(yè)務(wù)邏輯。
Filter在“pre”類(lèi)型的過(guò)濾器可以做參數(shù)校驗(yàn)、權(quán)限校驗(yàn)、流量監(jiān)控、日志輸出、協(xié)議轉(zhuǎn)換等。在“post”類(lèi)型的過(guò)濾器中可以做響應(yīng)內(nèi)容、響應(yīng)頭的修改,日志的輸出,流量監(jiān)控等有著非常重更的作用。
五、Gateway項(xiàng)目集成與配置
具體項(xiàng)目集成可查看:SpringCloud Gateway網(wǎng)關(guān)項(xiàng)目集成與配置
六、Gateway動(dòng)態(tài)路由
默認(rèn)情況下Gateway會(huì)根據(jù)注冊(cè)中心注冊(cè)的服務(wù)列表,以注冊(cè)中心上微服務(wù)名為路徑創(chuàng)建動(dòng)態(tài)路由進(jìn)行轉(zhuǎn)發(fā),從而實(shí)現(xiàn)動(dòng)態(tài)路由的功能。
1、配置文件增加開(kāi)啟路由的配置
discovery:
locator:
enabled: true #開(kāi)啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能,利用微服務(wù)名進(jìn)行路由
2、將之前寫(xiě)死的uri換成微服務(wù)名稱(chēng)(注冊(cè)中心上顯示的服務(wù)名)
uri: lb://cloud-payment #匹配后提供服務(wù)的路由地址
需要注意的是uri的協(xié)議為lb,表示啟用Gateway的負(fù)載均衡功能。
lb://serviceName是spring cloudgateway在微服務(wù)中自動(dòng)為我們創(chuàng)建的負(fù)載均衡uri

完整配置:
server:
port: 9527
spring:
application:
name: cloud-gateway #微服務(wù)應(yīng)用的名字
cloud:
gateway:
discovery:
locator:
enabled: true #開(kāi)啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能,利用微服務(wù)名進(jìn)行路由
routes:
- id: payment_routh #payment_route #路由的ID,沒(méi)有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb://cloud-payment #匹配后提供服務(wù)的路由地址
predicates:
- Path=/payment/timeout/** # 斷言,路徑相匹配的進(jìn)行路由
- id: payment_routh2 #payment_route #路由的ID,沒(méi)有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb://cloud-payment #匹配后提供服務(wù)的路由地址
predicates:
- Path=/payment/ok/** # 斷言,路徑相匹配的進(jìn)行路由
eureka:
client:
register-with-eureka: true #向注冊(cè)中心注冊(cè)自己
fetch-registry: true #從EurekaServer抓取已有的注冊(cè)信息,集群必須設(shè)置成true,才能配合ribbon負(fù)載均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka
instance:
instance-id: gateway9527 #主機(jī)名稱(chēng)修改
prefer-ip-address: true #訪問(wèn)路徑可以顯示ip
hostname: cloud-gateway-service
測(cè)試負(fù)載均衡效果, 8001/8002兩個(gè)端口切換。
七、Gateway的Predicate
1、網(wǎng)關(guān)項(xiàng)目啟動(dòng)時(shí),控制臺(tái)可以看到有很多種類(lèi)型的斷言。我們上邊演示的是Path類(lèi)型。

Spring Cloud Gateway包括許多內(nèi)置的Route Predicate工廠。
所有這些Predicate都與HTTP請(qǐng)求的不同屬性匹配。多個(gè)Route Predicate工廠可以進(jìn)行組合。
Spring Cloud Gateway 創(chuàng)建 Route 對(duì)象時(shí), 使用 RoutePredicateFactory 創(chuàng)建 Predicate 對(duì)象,Predicate 對(duì)象可以賦值給 Route。
2、 具體各種斷言配置參考官網(wǎng)示例:

3、演示一種,以After為例:
//官網(wǎng)上美國(guó)時(shí)間,我們獲取當(dāng)前時(shí)間:
public static void main(String[] args) {
ZonedDateTime obj=ZonedDateTime.now();
System.out.println(obj);
}
輸出:2023-06-06T15:36:36.892+08:00[Asia/Shanghai]
spring:
application:
name: cloud-gateway #微服務(wù)應(yīng)用的名字
cloud:
gateway:
discovery:
locator:
enabled: true #開(kāi)啟從注冊(cè)中心動(dòng)態(tài)創(chuàng)建路由的功能,利用微服務(wù)名進(jìn)行路由
routes:
- id: payment_routh #payment_route #路由的ID,沒(méi)有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb://cloud-payment #匹配后提供服務(wù)的路由地址
predicates:
- Path=/payment/timeout/** # 斷言,路徑相匹配的進(jìn)行路由
- After=2023-06-06T15:36:36.892+08:00[Asia/Shanghai] #
- id: payment_routh2 #payment_route #路由的ID,沒(méi)有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb://cloud-payment #匹配后提供服務(wù)的路由地址
predicates:
- Path=/payment/ok/** # 斷言,路徑相匹配的進(jìn)行路由
After斷言表示:在這個(gè)時(shí)間后,請(qǐng)求才有效果,在這個(gè)時(shí)間前,請(qǐng)求會(huì)404
八、Gateway的Filter
1、過(guò)濾器查看官網(wǎng),37種之多(不常用)

參照官網(wǎng)添加相應(yīng)的filters標(biāo)簽配置即可:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
2、查看官網(wǎng),自定義過(guò)濾器10種之多(比較常用)

自定義全局GlobalFilter主要用于全局日志記錄、統(tǒng)一網(wǎng)關(guān)鑒權(quán)等。
實(shí)現(xiàn)兩個(gè)重要接口
GlobalFilter,Ordered,容器注入,即可。
代碼示例:
package com.qytest.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
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 LogFilter implements GlobalFilter, Ordered {
@Bean
public GlobalFilter customFilter() {
return new LogFilter();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("start global filter" + new Date());
//假設(shè)請(qǐng)求參數(shù)沒(méi)有username認(rèn)為非法用戶,打印在日志中
String username = exchange.getRequest().getQueryParams().getFirst("username");
if (username == null) {
System.out.println("username 為空,非法用戶!");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
測(cè)試訪問(wèn)(不帶username):http://localhost:9527/payment/ok/1

