1 SpringCloud技術(shù)棧
- 清楚SpringCloud技術(shù)棧分類
- 能夠說出SpringCloud Gateway的工作流程
- 至少掌握Gateway動(dòng)態(tài)路由配置中基于Path的路由方式
- 能實(shí)現(xiàn)全局過濾器和局部過濾器的創(chuàng)建和使用
- 能寫出SpringCloud Gateway跨域配置
- 理解限流漏桶算法
- 能夠?qū)崿F(xiàn)基于漏桶算法的限流操作
開發(fā)分布式系統(tǒng)可能具有挑戰(zhàn)性,復(fù)雜性已從應(yīng)用程序?qū)愚D(zhuǎn)移到網(wǎng)絡(luò)層,并要求服務(wù)之間進(jìn)行更多的交互。將代碼設(shè)為“cloud-native”就需要解決12-factor,例如外部配置,服務(wù)無狀態(tài),日志記錄以及連接到備份服務(wù)之類的問題,Spring Cloud項(xiàng)目套件包含使您的應(yīng)用程序在云中運(yùn)行所需的許多服務(wù)。
12-factor(云原生應(yīng)用程序的12要素):
SpringCloud架構(gòu):
1.1 SpringCloud技術(shù)棧
SpringCloud技術(shù)棧非常豐富,這也是SpringCloud為什么在微服務(wù)領(lǐng)域中如此受歡迎的原因之一,技術(shù)棧如上圖,在服務(wù)注冊(cè)與配置、服務(wù)調(diào)用、微服務(wù)網(wǎng)關(guān)、消息組件、鏈路追蹤、配置中心、安全控制、將極限流等諸多方面技術(shù)棧都比較完善,而且阿里巴巴也出了一套SpringCloud Alibaba版本,主要集成了Alibaba中主流的技術(shù)棧。
1.2 SpringCloud經(jīng)典技術(shù)介紹
微服務(wù)項(xiàng)目近幾年非?;鸨瞥鰜淼南嚓P(guān)技術(shù)解決方案熱度也非?;钴S,但SpringCloud技術(shù)棧中也有一部分技術(shù)組件在逐步被淘汰或者閉源,但都有更優(yōu)秀的技術(shù)方案替代。在不久的將來,那些閉源或者將被淘汰的技術(shù)有很大概率將不在項(xiàng)目中使用,所以我們學(xué)習(xí)的時(shí)候可以直接學(xué)習(xí)更優(yōu)秀的替代技術(shù)方案。
Eureka閉源:
上面英文大概意思是:Eureka 2.0
的開源工作已經(jīng)停止,依賴于開源庫里面的 Eureka 2.x
分支構(gòu)建的項(xiàng)目或者相關(guān)代碼,風(fēng)險(xiǎn)自負(fù)。
Eureka在微服務(wù)項(xiàng)目中主要承擔(dān)服務(wù)注冊(cè)與發(fā)現(xiàn)工作,可以替代的技術(shù)方案比較多,而且很多方案都比Eureka優(yōu)秀,比如Consul、Nacos等。
Hystrix停止更新:
Hystrix在項(xiàng)目中主要做服務(wù)熔斷、降級(jí),但官方宣布將不在開發(fā),目前處于維護(hù)狀態(tài),但官方表示 1.5.18 版本的 Hystrix 已經(jīng)足夠穩(wěn)定,可以滿足 Netflix 現(xiàn)有應(yīng)用的需求。
關(guān)于Hystrix可替代的產(chǎn)品也比較多,比如官方推薦的resilience4j
,resilience4j
是一個(gè)輕量級(jí)熔斷框架,但resilience4j
目前在國內(nèi)使用頻率還不高,功能也不夠強(qiáng),我們更推薦使用功能更加強(qiáng)悍的SpringCloud Alibaba Sentinel
。
Zuul過時(shí):
Zuul
是一個(gè)微服務(wù)網(wǎng)關(guān)技術(shù),但Zuul1.x
使用的是阻塞式的API,不支持長連接,沒有提供異步,高并發(fā)場(chǎng)景下性能低。SpringCloud
官網(wǎng)推出了全新的微服務(wù)網(wǎng)關(guān)技術(shù)SpringCloud Gateway
,比Zuul
性能更強(qiáng)悍、功能更豐富、且支持異步等多種特性。
SpringCloud Config實(shí)用性差:
SpringCloud Config
主要用于管理項(xiàng)目的配置文件,每次要使用SpringCloud Config
的時(shí)候,總得經(jīng)過一波操作和配置的折騰,才可以使用SpringCloud Config
實(shí)現(xiàn)配置管理,而且單獨(dú)使用無法支持配置實(shí)時(shí)刷新,在項(xiàng)目中用起來,真比較頭疼的。
當(dāng)前有很多技術(shù)可以取代SpringCloud Config
,比如攜程的Apollo
、SpringCloud Alibaba Nacos
,功能都比SpringCloud Config
強(qiáng),并且支持實(shí)時(shí)刷新配置。
SpringCloud Bus實(shí)用性差:
SpringCloud Bus是服務(wù)消息總線,主要實(shí)現(xiàn)通知多個(gè)服務(wù)執(zhí)行某個(gè)任務(wù),一般和SpringCloud Config一起使用。這個(gè)功能其實(shí)不太使用,因?yàn)楹芏嗳蝿?wù)組件基本都具備消息通知功能,比如Nacos、Apollo都能實(shí)現(xiàn)所有服務(wù)訂閱執(zhí)行相關(guān)操作。
1.3 SpringCloud項(xiàng)目場(chǎng)景
微服務(wù)技術(shù)目前已經(jīng)在很多國內(nèi)外大廠中都在廣泛使用,那么在項(xiàng)目中該如何使用微服務(wù)技術(shù)呢?我們以滴滴快車業(yè)務(wù)未來,來講解一下微服務(wù)技術(shù)結(jié)合業(yè)務(wù)應(yīng)用講解一下。
打車業(yè)務(wù)如上圖:
1:打車的時(shí)候會(huì)選擇車型,選擇車型我們調(diào)用過程是:Gateway->Driver(加載司機(jī)列表)
2:選擇車型后確認(rèn)打車,相當(dāng)于要下單了,調(diào)用過程是:Gateway->Order(下單)->Driver(司機(jī)狀態(tài)更改)
3:打車結(jié)束后,用戶進(jìn)入支付,調(diào)用過程是:Gateway->Pay(支付)->Driver(更新司機(jī)狀態(tài))
->Order(更新訂單狀態(tài))
2 SpringCloud Gateway
Spring Cloud Gateway 是Spring Cloud團(tuán)隊(duì)的一個(gè)全新項(xiàng)目,基于Spring 5.0、SpringBoot2.0、Project Reactor 等技術(shù)開發(fā)的網(wǎng)關(guān)。旨在為微服務(wù)架構(gòu)提供一種簡單有效統(tǒng)一的API路由管理方式。
Spring Cloud Gateway 作為SpringCloud生態(tài)系統(tǒng)中的網(wǎng)關(guān),目標(biāo)是替代Netflix Zuul。Gateway不僅提供統(tǒng)一路由方式,并且基于Filter鏈的方式提供網(wǎng)關(guān)的基本功能。例如:安全,監(jiān)控/指標(biāo),和限流。
總結(jié):微服務(wù)網(wǎng)關(guān)就是一個(gè)系統(tǒng),通過暴露該微服務(wù)網(wǎng)關(guān)系統(tǒng),方便我們進(jìn)行相關(guān)的鑒權(quán),安全控制,日志統(tǒng)一處理,易于監(jiān)控,限流等相關(guān)功能。
實(shí)現(xiàn)微服務(wù)網(wǎng)關(guān)的技術(shù)有很多,
- nginx:Nginx (engine x) 是一個(gè)高性能的HTTP和反向代理web服務(wù)器,同時(shí)也提供了IMAP/POP3/SMTP服務(wù)
- zuul :Zuul 是 Netflflix 出品的一個(gè)基于 JVM 路由和服務(wù)端的負(fù)載均衡器。
- spring-cloud-gateway:是spring 出品的基于spring的網(wǎng)關(guān)項(xiàng)目,集成斷路器,路徑重寫,性能比Zuul好。
我們使用gateway這個(gè)網(wǎng)關(guān)技術(shù),無縫銜接到基于spring cloud的微服務(wù)開發(fā)中來。
gateway官網(wǎng):
https://spring.io/projects/spring-cloud-gateway
2.1 Gateway工作原理
我們?cè)趯W(xué)習(xí)Gateway之前,先弄清楚Gateway的工作原理,后面使用它的各個(gè)功能時(shí),就知道該如何使用了,工作流程圖如下:
Gateway的執(zhí)行流程如下:
1:Gateway的客戶端回向Spring Cloud Gateway發(fā)起請(qǐng)求,請(qǐng)求首先會(huì)被HttpWebHandlerAdapter進(jìn)行提取組裝成網(wǎng)關(guān)的上下文,然后網(wǎng)關(guān)的上下文會(huì)傳遞到DispatcherHandler。
2:DispatcherHandler是所有請(qǐng)求的分發(fā)處理器,DispatcherHandler主要負(fù)責(zé)分發(fā)請(qǐng)求對(duì)應(yīng)的處理器,比如將請(qǐng)求分發(fā)到對(duì)應(yīng)RoutePredicateHandlerMapping(路由斷言處理器映射器)。
3:路由斷言處理映射器主要用于路由的查找,以及找到路由后返回對(duì)應(yīng)的FilteringWebHandler。
4:FilteringWebHandler主要負(fù)責(zé)組裝Filter鏈表并調(diào)用Filter執(zhí)行一系列Filter處理,然后把請(qǐng)求轉(zhuǎn)到后端對(duì)應(yīng)的代理服務(wù)處理,處理完畢后,將Response返回到Gateway客戶端。
在Filter鏈中,通過虛線分割Filter的原因是,過濾器可以在轉(zhuǎn)發(fā)請(qǐng)求之前處理或者接收到被代理服務(wù)的返回結(jié)果之后處理。所有的Pre類型的Filter執(zhí)行完畢之后,才會(huì)轉(zhuǎn)發(fā)請(qǐng)求到被代理的服務(wù)處理。被代理的服務(wù)把所有請(qǐng)求完畢之后,才會(huì)執(zhí)行Post類型的過濾器。
2.2 Gateway路由
Gateway路由配置分為基于配置的靜態(tài)路由設(shè)置和基于代碼動(dòng)態(tài)路由配置,
靜態(tài)路由是指在application.yml中把路由信息配置好了,而動(dòng)態(tài)路由則支持在代碼中動(dòng)態(tài)加載路由信息,更加靈活,我們接下來把這2種路由操作都實(shí)現(xiàn)一次。
2.2.1 業(yè)務(wù)說明
如上圖:
1:用戶所有請(qǐng)求以/order開始的請(qǐng)求,都路由到 hailtaxi-order服務(wù)
2:用戶所有請(qǐng)求以/driver開始的請(qǐng)求,都路由到 hailtaxi-driver服務(wù)
3:用戶所有請(qǐng)求以/pay開始的請(qǐng)求,都路由到 hailtaxi-pay服務(wù)
2.2.2 基于配置路由設(shè)置
如上圖所示,正是Gateway靜態(tài)路由配置:
1:用戶所有請(qǐng)求以/order開始的請(qǐng)求,都路由到 hailtaxi-order服務(wù)
2:用戶所有請(qǐng)求以/driver開始的請(qǐng)求,都路由到 hailtaxi-driver服務(wù)
3:用戶所有請(qǐng)求以/pay開始的請(qǐng)求,都路由到 hailtaxi-pay服務(wù)
配置參數(shù)說明:
routes:路由配置
- id:唯一標(biāo)識(shí)符
uri:路由地址,可以是 lb://IP:端口 也可以是 lb://${spring.application.name}
predicates:斷言,是指路由條件
- Path=/driver/**:路由條件。Predicate 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果。這里表示匹配所有以driver開始的請(qǐng)求。
filters:過濾器
- StripPrefix=1:真實(shí)路由的時(shí)候,去掉第1個(gè)路徑,路徑個(gè)數(shù)以/分割區(qū)分
測(cè)試url:http://localhost:8001/driver/info/1
2.2.3 基于代碼路由配置
我們同樣實(shí)現(xiàn)上面的功能,但這里基于代碼方式實(shí)現(xiàn)。所有路由規(guī)則我們可以從數(shù)據(jù)庫中讀取并加載到程序中?;诖a的路由配置我們只需要?jiǎng)?chuàng)建RouteLocator
并添加路由配置即可,代碼如下:
/***
* 路由配置
* @param builder
* @return
*/
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
.route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
.route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
.build();
}
在真實(shí)場(chǎng)景中,基于配置文件的方式更直觀、簡介,但代碼的路由配置是更強(qiáng)大,可以實(shí)現(xiàn)很豐富的功能,可以把路由規(guī)則存在數(shù)據(jù)庫中,每次直接從數(shù)據(jù)庫中加載規(guī)則,這樣的好處是可以動(dòng)態(tài)刷新路由規(guī)則,通常應(yīng)用于權(quán)限系統(tǒng)動(dòng)態(tài)配置。
spring:
cloud:
gateway:
#路由配置
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
- Path=/driver/**
#唯一標(biāo)識(shí)符
- id: hailtaxi-order
uri: lb://hailtaxi-order
#路由斷言
predicates:
- Path=/order/**
#唯一標(biāo)識(shí)符
- id: hailtaxi-pay
uri: lb://hailtaxi-pay
#路由斷言
predicates:
- Path=/pay/**
2.2.4 Gateway-Predicate
上面路由匹配規(guī)則中我們都用了- Path
方式,其實(shí)就是路徑匹配方式,除了路徑匹配方式,Gateway還支持很多豐富的匹配方式,我們對(duì)這些方式分別進(jìn)行講解。
關(guān)于Predicate
學(xué)習(xí)地址,可以參考官網(wǎng):
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories
或者:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#gateway-request-predicates-factories
routes下面的屬性含義如下:
id:我們自定義的路由 ID,保持唯一
uri:目標(biāo)服務(wù)地址
predicates:路由條件,Predicate 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果。該屬性包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)
Predicate 來源于 Java 8,Predicate 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果。該接口包含多種默認(rèn)方法來將 Predicate 組合成其他復(fù)雜的邏輯(比如:與,或,非)。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性實(shí)現(xiàn)了各種路由匹配規(guī)則,通過 Header、請(qǐng)求參數(shù)等不同的條件來作為條件匹配到對(duì)應(yīng)的路由。
下面的一張圖(來自網(wǎng)絡(luò))總結(jié)了 Spring Cloud 內(nèi)置的幾種 Predicate 的實(shí)現(xiàn):
我們?cè)谶@里講解幾個(gè)斷言匹配 方式。
Cookie:
Gateway的Cookie匹配接收兩個(gè)參數(shù):一個(gè)是 Cookie name ,一個(gè)是正則表達(dá)式。路由規(guī)則就是通過獲取對(duì)應(yīng)的 Cookie name 值和正則表達(dá)式去匹配,如果匹配上就會(huì)執(zhí)行路由,如果沒有匹配上則不執(zhí)行。如下配置:
gateway:
routes:
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
predicates:
- Path=/driver/**
- Cookie=username,itheima
這里表示請(qǐng)求攜帶了cookie為username的數(shù)據(jù),并且值為itheima,就允許通過。
Header 匹配:
Header 匹配 和 Cookie 匹配 一樣,也是接收兩個(gè)參數(shù),一個(gè) header 中屬性名稱和一個(gè)正則表達(dá)式,這個(gè)屬性值和正則表達(dá)式匹配則執(zhí)行。配置如下:
gateway:
routes:
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
predicates:
- Path=/driver/**
- Header=token,^(?!\d+$)[\da-zA-Z]+$
上面的匹配規(guī)則,就是請(qǐng)求頭要有token屬性,并且值必須為數(shù)字和字母組合的正則表達(dá)式,例如攜帶token=19and30
就可以通過訪問。
請(qǐng)求方式匹配:
通過請(qǐng)求的方式是 POST、GET、PUT、DELETE 等進(jìn)行路由。配置如下:
gateway:
routes:
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
predicates:
- Path=/driver/**
- Method=GET,POST
通過yml
的完整代碼如下(注釋掉java的配置)
server:
port: 8001
spring:
application:
name: hailtaxi-gateway
main:
allow-bean-definition-overriding: true
cloud:
#Consul配置
consul:
host: 127.0.0.1
port: 8500
discovery:
#注冊(cè)到Consul中的服務(wù)名字
service-name: ${spring.application.name}
#注冊(cè)的服務(wù)的實(shí)例 Id,最好不要重復(fù),這里參考官網(wǎng)建議的方式 帶隨機(jī)數(shù) 默認(rèn):應(yīng)用名:port
#instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.i nstance_id:${random.value}}}
# 自定義實(shí)例id為:應(yīng)用名:ip:port
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true
# 開啟服務(wù)注冊(cè)
register: true
# 開啟服務(wù)發(fā)現(xiàn)
enabled: true
#2 分鐘之后健康檢查未通過取消注冊(cè)
health-check-critical-timeout: 2m
#consul 健康檢查的輪詢周期
health-check-interval: 10s
gateway:
#路由配置
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
- Path=/driver/**
- Cookie=username,itheima
- Header=token,^(?!\d+$)[\da-zA-Z]+$
- Method=GET,POST
#唯一標(biāo)識(shí)符
- id: hailtaxi-order
uri: lb://hailtaxi-order
#路由斷言
predicates:
- Path=/order/**
#唯一標(biāo)識(shí)符
- id: hailtaxi-pay
uri: lb://hailtaxi-pay
#路由斷言
predicates:
- Path=/pay/**
2.2.5 斷言源碼剖析
拿Cookie
斷言來說,首先看它的體系結(jié)構(gòu)
public class CookieRoutePredicateFactory
extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {
/**
* Name key.
*/
public static final String NAME_KEY = "name";
/**
* Regexp key.
*/
public static final String REGEXP_KEY = "regexp";
public CookieRoutePredicateFactory() {
super(Config.class);
}
/*
通過shortcutFieldOrder方法設(shè)置Config配置類中的屬性,需要根據(jù)具體的規(guī)則來設(shè)置
通過shortcutType方法獲取具體規(guī)則,具體參看:org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType
規(guī)則包括以下幾種:
DEFAULT : 按照shortcutFieldOrder順序依次賦值
*/
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(NAME_KEY, REGEXP_KEY);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
List<HttpCookie> cookies = exchange.getRequest().getCookies()
.get(config.name);
if (cookies == null) {
return false;
}
for (HttpCookie cookie : cookies) {
if (cookie.getValue().matches(config.regexp)) {
return true;
}
}
return false;
}
@Override
public String toString() {
return String.format("Cookie: name=%s regexp=%s", config.name,
config.regexp);
}
};
}
/*
內(nèi)部配置類是用來接收在配置文件中配置的參數(shù)的
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
- Cookie=username,itheima
*/
@Validated
public static class Config {
@NotEmpty
private String name;
@NotEmpty
private String regexp;
public String getName() {
return name;
}
public Config setName(String name) {
this.name = name;
return this;
}
public String getRegexp() {
return regexp;
}
public Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}
盡管Spring Cloud Gateway已經(jīng)包含了很多路由匹配規(guī)則,有時(shí)候我們需要開發(fā)自定義路由匹配規(guī)則來滿足需求,下面簡單的介紹一下如何自定義路由匹配規(guī)則。
案例
需求:轉(zhuǎn)發(fā)帶token的請(qǐng)求到hailtaxi-drvier
服務(wù)中,這里定義請(qǐng)求帶token是指包含某個(gè)請(qǐng)求頭的請(qǐng)求,至于是什么請(qǐng)求頭可以由配置指定
1、修改配置文件
gateway:
#路由配置
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
# 自定義一個(gè)Token斷言,如果請(qǐng)求包含Authorization的token信息則通過
- Token=Authorization
2、創(chuàng)建 RoutePredicateFactory
斷言工廠默認(rèn)命名規(guī)則必須按照"名稱"+RoutePredicateFactory,如上TokenRoutePredicateFactory的斷言名稱為Token
@Slf4j
@Component // 要交給spring容器管理
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {
public TokenRoutePredicateFactory() {
super(Config.class);
}
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
// 打印配置文件參數(shù)值
String headerName = config.getHeaderName();
HttpHeaders headers = exchange.getRequest().getHeaders();
List<String> header = headers.get(headerName);
log.info("Token Predicate headers:{}", header);
// 斷言返回的是boolean值
return header!=null && header.size()>0;
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("headerName");//指定配置文件中加載到的配置信息應(yīng)填充到Config的哪個(gè)屬性上
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}
@Data
public static class Config { //static class
private String headerName;//存儲(chǔ)從配置文件中加載的配置
}
}
啟動(dòng)測(cè)試:http://localhost:8001/driver/info/1
2.3 Gateway過濾器
Spring Cloud Gateway根據(jù)作用范圍劃分為GatewayFilter和GlobalFilter,二者區(qū)別如下:
- GatewayFilter : 需要通過spring.cloud.routes.filters 配置在具體路由下,只作用在當(dāng)前路由上或通過spring.cloud.default-filters配置在全局,作用在所有路由上;gateway內(nèi)置了多種過濾器工廠,配套的過濾器可以直接使用,如下圖所示:
- GlobalFilter : 全局過濾器,不需要在配置文件中配置,作用在所有的路由上,最終通過GatewayFilterAdapter包裝成GatewayFilterChain可識(shí)別的過濾器,它為請(qǐng)求業(yè)務(wù)以及路由的URI轉(zhuǎn)換為真實(shí)業(yè)務(wù)服務(wù)的請(qǐng)求地址的核心過濾器,不需要配置,系統(tǒng)初始化時(shí)加載,并作用在每個(gè)路由上。
過濾器作為Gateway的重要功能。常用于請(qǐng)求鑒權(quán)、服務(wù)調(diào)用時(shí)長統(tǒng)計(jì)、修改請(qǐng)求或響應(yīng)header、限流、去除路徑等等。
關(guān)于Gateway過濾器的更多使用,大家可以參考官方地址:
https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories
或者:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
2.3.1 過濾器分類
默認(rèn)過濾器:出廠自帶,實(shí)現(xiàn)好了拿來就用,不需要實(shí)現(xiàn)
全局默認(rèn)過濾器
局部默認(rèn)過濾器
自定義過濾器:根據(jù)需求自己實(shí)現(xiàn),實(shí)現(xiàn)后需配置,然后才能用哦。
全局過濾器:作用在所有路由上。
局部過濾器:配置在具體路由下,只作用在當(dāng)前路由上。
默認(rèn)過濾器十好幾個(gè),常見如下:
過濾器名稱 | 說明 | 對(duì)應(yīng)的類 | 父類 |
---|---|---|---|
AddRequestHeader | 對(duì)匹配上的請(qǐng)求加上Header | AddRequestHeaderGatewayFilterFactory | AbstractNameValueGatewayFilterFactory |
AddRequestParameters | 對(duì)匹配上的請(qǐng)求路由 | AddRequestHeaderGatewayFilterFactory | AbstractNameValueGatewayFilterFactory |
AddResponseHeader | 對(duì)從網(wǎng)關(guān)返回的響應(yīng)添加Header | AddResponseHeaderGatewayFilterFactory | AbstractNameValueGatewayFilterFactory |
StripPrefix | 對(duì)匹配上的請(qǐng)求路徑去除前綴 | StripPrefixGatewayFilterFactory | AbstractGatewayFilterFactory |
2.3.2 默認(rèn)過濾器的使用
所謂默認(rèn)過濾器就是系統(tǒng)自帶的。有很多,這里簡要說明幾個(gè):(通過java配置,注釋掉yaml配置)
1)添加響應(yīng)頭
AddResponseHeaderGatewayFilterFactory 屬于 GatewayFilter
對(duì)輸出響應(yīng)頭設(shè)置屬性,比如對(duì)輸出的響應(yīng)設(shè)置其頭部屬性名稱為:X-Response-Default-MyName , 值為itheima
修改配置文件,配置如下:
spring:
cloud:
gateway:
# 配置全局默認(rèn)過濾器 作用在所有路由上,也可單獨(dú)為某個(gè)路由配置
default-filters:
# 往響應(yīng)過濾器中加入信息
- AddResponseHeader=X-Response-Default-MyName,itheima
請(qǐng)求http://localhost:8001/driver/info/1
,響應(yīng)數(shù)據(jù)添加了X-Response-Default-MyName: itheima
,如下圖:
2)前綴處理
在項(xiàng)目中做開發(fā)對(duì)接接口的時(shí)候,我們很多時(shí)候需要統(tǒng)一API路徑,比如統(tǒng)一以/api
開始的請(qǐng)求調(diào)用hailtaxi-driver
服務(wù),但真實(shí)服務(wù)接口地址又沒有/api
路徑,我們可以使用Gateway的過濾器處理請(qǐng)求路徑。
在gateway中可以通過配置路由的過濾器StripPrefix實(shí)現(xiàn)映射路徑中的前綴處理,我們來使用一下該過濾器,再進(jìn)一步做說明。
gateway:
routes:
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
predicates:
- Path=/api/driver/**
filters:
- StripPrefix=1
此處- StripPrefix=1
表示真實(shí)請(qǐng)求地址是當(dāng)前用戶請(qǐng)求以/api
開始的uri中去除第1個(gè)路徑/api
.
上面配置最終執(zhí)行如下表:
配置 | 路由地址 | 訪問地址 |
---|---|---|
StripPrefix=1 | http://localhost:8001/api/driver/info/2 | http://localhost:18081/driver/info/2 |
StripPrefix=2 | http://localhost:8001/api/suri/driver/info/2 | http://localhost:18081/driver/info/2 |
有時(shí)候?yàn)榱撕喕脩粽?qǐng)求地址,比如用戶請(qǐng)求http://localhost:8001/info/1
我們想統(tǒng)一路由到http://localhost:18081/driver/info/1
,可以使用PrefixPath
過濾器增加前綴。
gateway:
routes:
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
predicates:
- Path=/**
filters:
- PrefixPath=/driver
上面配置最終執(zhí)行如下表:
配置 | 路由地址 | 訪問地址 |
---|---|---|
- PrefixPath=/driver | http://localhost:8001/info/2 | http://localhost:18081/driver/info/2 |
2.3.3自定義GatewayFilter
1、實(shí)現(xiàn)GatewayFilter接口
GatewayFilter 一般作用在某一個(gè)路由上,需要實(shí)例化創(chuàng)建才能使用,局部過濾器需要實(shí)現(xiàn)接口GatewayFilter、Ordered
。
創(chuàng)建com.itheima.filter.PayFilter
代碼如下:
public class PayFilter implements GatewayFilter,Ordered {
/***
* 過濾器執(zhí)行攔截
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("GatewayFilter攔截器執(zhí)行---pre-----PayFilter");
return chain.filter(exchange).then(Mono.fromRunnable(()->{
System.out.println("GatewayFilter攔截器執(zhí)行---post-----PayFilter");
}));
}
@Override
public int getOrder() {
return 0;
}
}
使用局部過濾器:(使用下面RouteLocator的時(shí)候,配置文件中的路由記得注釋或刪除)
/***
* 路由配置
* @param builder
* @return
*/
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("hailtaxi-driver", r -> r.path("/api/driver/**")
.and().cookie("username","itheima")
.and().header("token","123456")
.filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima")
.addRequestHeader("myheader", "1234567")
.stripPrefix(1)
)
// .and().method(HttpMethod.POST)
.uri("lb://hailtaxi-driver")
//.filter(new PayFilter())
)
.route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
.route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
.build();
}
為了更好看到效果,我們?cè)?code>RouterFilter添加System.out.println("GlobalFilter攔截器執(zhí)行");
再訪問測(cè)試。
訪問:http://localhost:8001/api/driver/info/1,注意使用postman發(fā)送請(qǐng)求時(shí)添加請(qǐng)求頭,添加cookie。
2、繼承GatewayFilterFactory
如果定義局部過濾器,想在配置文件中進(jìn)行配置來使用,可以繼承AbstractGatewayFilterFactory<T>
抽象類或者AbstractNameValueGatewayFilterFactory
整個(gè)體系結(jié)構(gòu)為:
這兩個(gè)抽象類的區(qū)別就是前者接收一個(gè)參數(shù)(像StripPrefix和我們創(chuàng)建的這種),后者接收兩個(gè)參數(shù)(像AddResponseHeader)
代碼的編寫可以參考:StripPrefixGatewayFilterFactory
和 AddRequestHeaderGatewayFilterFactory
過濾器工廠默認(rèn)命名規(guī)則必須按照"名稱"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的過濾器名稱為StripPrefix
2.1、繼承AbstractGatewayFilterFactory
需求:
在網(wǎng)關(guān)中統(tǒng)一支付方式,編寫一個(gè)過濾器:PayMethodGatewayFilterFactory
,
1、編寫過濾器
@Slf4j
@Component //一定要將其交給spring容器管理
public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Config> {
public PayMethodGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String paymethod = config.getPayMethod();
String msg = config.getMsg();
log.info("PayMethodGatewayFilterFactory 加載到的配置信息為:{}---{}",paymethod,msg);
//將paymethod添加到請(qǐng)求頭中
exchange.getRequest().mutate().header("paymethod",paymethod);
return chain.filter(exchange);
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("payMethod","msg");//指定從yml中提前出來的配置信息填充到配置類中哪個(gè)屬性,按規(guī)則配置
}
@Override
public ShortcutType shortcutType() {
return ShortcutType.DEFAULT;//默認(rèn)規(guī)則
}
/**
* 加載從yml中提取出來的配置信息
*/
@Data
public static class Config {
private String payMethod;
private String msg;
}
}
2、配置文件中使用如下:
gateway:
#路由配置
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
- Path=/driver/**
- Cookie=username,itheima
- Header=token,^(?!\d+$)[\da-zA-Z]+$
- Method=GET,POST
- Token=Authorization
filters:
- PayMethod=alipay,業(yè)務(wù)整合
再次測(cè)試,查看hailtaxi-driver 服務(wù)接收到請(qǐng)求后是否多了paymethod
請(qǐng)求頭信息
2.2、繼承AbstractNameValueGatewayFilterFactory
直接查看AddRequestHeaderGatewayFilterFactory
源碼,分析即可!
2.3.4 自定義GlobalFilter
定義全局過濾器需要實(shí)現(xiàn)GlobalFilter,Ordered接口:
GlobalFilter:過濾器攔截處理方法
Ordered:過濾器也有多個(gè),這里主要定義過濾器執(zhí)行順序,里面有個(gè)方法getOrder()會(huì)返回過濾器執(zhí)行順序,返回值越小,越靠前執(zhí)行
需求:
我們創(chuàng)建全局過濾器并完成常見業(yè)務(wù)用戶權(quán)限校驗(yàn),如果請(qǐng)求中有帶有一個(gè)名字為token
的請(qǐng)求參數(shù),則認(rèn)為請(qǐng)求有效放行,如果沒有則攔截提示授權(quán)無效。
創(chuàng)建全局過濾器:com.itheima.filter.RouterFilter
,代碼如下:
@Component
public class RouterFilter implements GlobalFilter,Ordered {
/***
* 路由攔截
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("RouterFilter----------------");
//獲取請(qǐng)求參數(shù)
String token = exchange.getRequest().getQueryParams().getFirst("token");
//如果token為空,則表示沒有登錄
if(StringUtils.isEmpty(token)){
//沒登錄,狀態(tài)設(shè)置403
exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
//結(jié)束請(qǐng)求
return exchange.getResponse().setComplete();
}
//放行
return chain.filter(exchange);
}
/***
* 攔截器順序
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
此時(shí)請(qǐng)求,我們不攜帶token參數(shù),效果如下:
我們攜帶token參數(shù)則可以正常訪問,效果如下:
2.4 跨域配置
出于瀏覽器的同源策略限制。同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會(huì)受到影響。可以說Web是構(gòu)建在同源策略基礎(chǔ)之上的,瀏覽器只是針對(duì)同源策略的一種實(shí)現(xiàn)。同源策略會(huì)阻止一個(gè)域的javascript腳本和另外一個(gè)域的內(nèi)容進(jìn)行交互。所謂同源(即指在同一個(gè)域)就是兩個(gè)頁面具有相同的協(xié)議(protocol),主機(jī)(host)和端口號(hào)(port)。
在Spring Cloud Gateway中配置跨域是非常簡單的,如下面application.yml
所示:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- PUT
另外一種寫法就需要?jiǎng)?chuàng)建CorsWebFilter
過濾器,代碼如下:
/**
* 配置跨域
* @return
*/
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// cookie跨域
config.setAllowCredentials(Boolean.TRUE);
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
// 配置前端js允許訪問的自定義響應(yīng)頭
config.addExposedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
2.5 限流
網(wǎng)關(guān)可以做很多的事情,比如,限流,當(dāng)我們的系統(tǒng) 被頻繁的請(qǐng)求的時(shí)候,就有可能 將系統(tǒng)壓垮,所以 為了解決這個(gè)問題,需要在每一個(gè)微服務(wù)中做限流操作,但是如果有了網(wǎng)關(guān),那么就可以在網(wǎng)關(guān)系統(tǒng)做限流,因?yàn)樗械恼?qǐng)求都需要先通過網(wǎng)關(guān)系統(tǒng)才能路由到微服務(wù)中。
2.5.1 令牌桶算法講解
令牌桶算法是常見的限流算法之一,我們講解一下漏桶算法:
1)所有的請(qǐng)求在處理之前都需要拿到一個(gè)可用的令牌才會(huì)被處理;
2)根據(jù)限流大小,設(shè)置按照一定的速率往桶里添加令牌;
3)桶設(shè)置最大的放置令牌限制,當(dāng)桶滿時(shí)、新添加的令牌就被丟棄或者拒絕;
4)請(qǐng)求達(dá)到后首先要獲取令牌桶中的令牌,拿著令牌才可以進(jìn)行其他的業(yè)務(wù)邏輯,處理完業(yè)務(wù)邏輯之后,將令牌直接刪除;
5)令牌桶有最低限額,當(dāng)桶中的令牌達(dá)到最低限額的時(shí)候,請(qǐng)求處理完之后將不會(huì)刪除令牌,以此保證足夠的限流
令牌桶算法的實(shí)現(xiàn),有很多技術(shù),Guaua是其中之一,redis客戶端也有其實(shí)現(xiàn)。
2.5.2 限流案例
spring cloud gateway 默認(rèn)使用redis的RateLimter限流算法來實(shí)現(xiàn),外面來簡要實(shí)現(xiàn)一下:
1、引入依賴
首先需要引入redis的依賴:
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
同時(shí)不要忘記Redis配置:
redis:
host: 127.0.0.1
port: 6379
2、定義KeyResolver
在Application引導(dǎo)類中添加如下代碼,KeyResolver用于計(jì)算某一個(gè)類型的限流的KEY也就是說,可以通過KeyResolver來指定限流的Key。
我們可以根據(jù)IP來限流,比如每個(gè)IP每秒鐘只能請(qǐng)求一次,在GatewayApplication定義key的獲取,獲取客戶端IP,將IP作為key,如下代碼:
/***
* IP限流
* @return
*/
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
//獲取遠(yuǎn)程客戶端IP
String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
System.out.println("hostName:"+hostName);
return Mono.just(hostName);
}
};
}
在路由中配置如下:
gateway:
#路由配置
routes:
#唯一標(biāo)識(shí)符
- id: hailtaxi-driver
uri: lb://hailtaxi-driver
#路由斷言
predicates:
- Path=/driver/**
- Cookie=username,itheima
- Header=token,^(?!\d+$)[\da-zA-Z]+$
- Method=GET,POST
- Token=Authorization
filters:
- PayMethod=alipay,業(yè)務(wù)整合
- name: RequestRateLimiter #請(qǐng)求數(shù)限流 名字不能隨便寫 ,使用默認(rèn)的facatory
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
參數(shù)說明:
redis-rate-limiter.replenishRate是您希望允許用戶每秒執(zhí)行多少請(qǐng)求,而不會(huì)丟棄任何請(qǐng)求。這是令牌桶填充的速率
redis-rate-limiter.burstCapacity是指令牌桶的容量,允許在一秒鐘內(nèi)完成的最大請(qǐng)求數(shù),將此值設(shè)置為零將阻止所有請(qǐng)求。
key-resolver: “#{@ipKeyResolver}” 用于通過SPEL表達(dá)式來指定使用哪一個(gè)KeyResolver.
如上配置: 表示 一秒內(nèi),允許 一個(gè)請(qǐng)求通過,令牌桶的填充速率也是一秒鐘添加一個(gè)令牌。 最大突發(fā)狀況 也只允許 一秒內(nèi)有一次請(qǐng)求,可以根據(jù)業(yè)務(wù)來調(diào)整 。
我們請(qǐng)求http://localhost:8001/driver/info/1?token=aa
執(zhí)行測(cè)試,效果如下:文章來源:http://www.zghlxwxcb.cn/news/detail-461977.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-461977.html
到了這里,關(guān)于SpringCloud Gateway高級(jí)應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!