接上一篇文章開始網(wǎng)關(guān)之旅蓝角,首先告訴大家網(wǎng)關(guān)是什么径簿,Gateway簡介,怎么配置俘种,怎么入門秤标,執(zhí)行流程等等相關(guān)介紹。
第一章:微服務(wù)的架構(gòu)介紹發(fā)展
第二章:微服務(wù)環(huán)境搭建
第三章:Nacos Discovery--服務(wù)治理
第四章:Sentinel--服務(wù)容錯
第五章:Gateway--服務(wù)網(wǎng)關(guān)
第六章:Sleuth--鏈路追蹤
第七章:Rocketmq--消息驅(qū)動
5.1網(wǎng)關(guān)簡介
大家都都知道在微服務(wù)架構(gòu)中宙刘,一個系統(tǒng)會被拆分為很多個微服務(wù)苍姜。那么作為客戶端要如何去調(diào)用
這么多的微服務(wù)呢?如果沒有網(wǎng)關(guān)的存在悬包,我們只能在客戶端記錄每個微服務(wù)的地址衙猪,然后分別去用。
這樣的架構(gòu)布近,會存在著諸多的問題:
客戶端多次請求不同的微服務(wù)垫释,增加客戶端代碼或配置編寫的復(fù)雜性
認(rèn)證復(fù)雜,每個服務(wù)都需要獨立認(rèn)證撑瞧。
存在跨域請求棵譬,在一定場景下處理相對復(fù)雜。
上面的這些問題可以借助API網(wǎng)關(guān)來解決预伺。
所謂的API網(wǎng)關(guān)订咸,就是指系統(tǒng)的統(tǒng)一入口,它封裝了應(yīng)用程序的內(nèi)部結(jié)構(gòu)酬诀,為客戶端提供統(tǒng)一服務(wù)脏嚷,一些與業(yè)務(wù)本身功能無關(guān)的公共邏輯可以在這里實現(xiàn),諸如認(rèn)證料滥、鑒權(quán)然眼、監(jiān)控艾船、路由轉(zhuǎn)發(fā)等等葵腹。
添加上API網(wǎng)關(guān)之后高每,系統(tǒng)的架構(gòu)圖變成了如下所示:
我們也可以觀察下,我們現(xiàn)在的整體架構(gòu)圖:
在業(yè)界比較流行的網(wǎng)關(guān)践宴,有下面這些:
Ngnix+lua
使用nginx的反向代理和負(fù)載均衡可實現(xiàn)對api服務(wù)器的負(fù)載均衡及高可用
lua是一種腳本語言,可以來編寫一些簡單的邏輯, nginx支持lua腳本
Kong
基于Nginx+Lua開發(fā)鲸匿,性能高,穩(wěn)定阻肩,有多個可用的插件(限流带欢、鑒權(quán)等等)可以開箱即用。問題:只支持Http協(xié)議烤惊;二次開發(fā)乔煞,自由擴展困難;提供管理API柒室,缺乏更易用的管控渡贾、配置方式。
Zuul Netflix
開源的網(wǎng)關(guān)雄右,功能豐富空骚,使用JAVA開發(fā),易于二次開發(fā) 問題:缺乏管控擂仍,無法動態(tài)配置囤屹;依賴組件較多;處理Http請求依賴的是Web容器逢渔,性能不如Nginx
Spring Cloud Gateway
Spring公司為了替換Zuul而開發(fā)的網(wǎng)關(guān)服務(wù)肋坚,將在下面具體介紹。
注意:SpringCloud Alibaba 技術(shù)棧中并沒有提供自己的網(wǎng)關(guān)复局,我們可以采用Spring Cloud Gateway來做網(wǎng)關(guān)
5.2 Gateway 簡介
Spring Cloud Gateway是Spring公司基于Spring 5.0冲簿,Spring Boot 2.0 和 Project Reactor 等技術(shù)開發(fā)的網(wǎng)關(guān),它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式亿昏。它的目標(biāo)是替代Netflix Zuul峦剔,其不僅提供統(tǒng)一的路由方式,并且基于 Filter 鏈的方式提供了網(wǎng)關(guān)基本的功能角钩,例如:安全吝沫,監(jiān)控和限流。
優(yōu)點:
- 性能強勁:是第一代網(wǎng)關(guān)Zuul的1.6倍
- 功能強大:內(nèi)置了很多實用的功能递礼,例如轉(zhuǎn)發(fā)惨险、監(jiān)控、限流等
- 設(shè)計優(yōu)雅脊髓,容易擴展
缺點:- 其實現(xiàn)依賴Netty與WebFlux辫愉,不是傳統(tǒng)的Servlet編程模型,學(xué)習(xí)成本高不能將其部署在Tomcat将硝、Jetty等Servlet容器里恭朗,只能打成jar包執(zhí)行
- 需要Spring Boot 2.0及以上的版本屏镊,才支持
5.3 Gateway 快速入門
要求: 通過瀏覽器訪問api網(wǎng)關(guān),然后通過網(wǎng)關(guān)將請求轉(zhuǎn)發(fā)到商品微服務(wù)
5.3.1 基礎(chǔ)版
1)創(chuàng)建一個 api-gateway 的模塊, 導(dǎo)入相關(guān)依賴
<?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">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<parent>
<artifactId>micro-mall</artifactId>
<groupId>com.bxs</groupId>
<version>1.0.0</version>
</parent>
<artifactId>mall-gateway</artifactId>
<name>${project.artifactId}</name>
<description>spring cloud gateway 網(wǎng)關(guān)</description>
<dependencies>
<!-- gateway 網(wǎng)關(guān) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
</dependencies>
</project>
2)創(chuàng)建主類 @SpringBootApplication
@SpringBootApplication
public class ApiGatewayApplication{
public static void main(String[] args) {
SpringApplication.run( GatewayApplication.class, args );
}
}
3)添加配置文件
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
gateway:
routes: # 路由數(shù)組[路由 就是指定當(dāng)請求滿足什么條件的時候轉(zhuǎn)到哪個微服務(wù)]
- id: product_route # 當(dāng)前路由的標(biāo)識, 要求唯一
uri: http://localhost:8081 # 請求要轉(zhuǎn)發(fā)到的地址
order: 1 # 路由的優(yōu)先級,數(shù)字越小級別越高
predicates: # 斷言(就是路由轉(zhuǎn)發(fā)要滿足的條件)
- Path=/product-serv/** # 當(dāng)請求路徑滿足Path指定的規(guī)則時,才進(jìn)行路由轉(zhuǎn)發(fā)
filters: # 過濾器,請求在傳遞過程中可以通過過濾器對其進(jìn)行一定的修改
- StripPrefix=1 # 轉(zhuǎn)發(fā)之前去掉1層路徑 |
4)啟動項目, 并通過網(wǎng)關(guān)去訪問微服務(wù)
5.3.2 增強版
1)現(xiàn)在在配置文件中寫死了轉(zhuǎn)發(fā)路徑的地址, 前面我們已經(jīng)分析過地址寫死帶來的問題, 接下來我們從注冊中心獲取此地址。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2)在主類上添加注解
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication{
public static void main(String[] args) {
SpringApplication.run( GatewayApplication.class, args );
}
}
3)修改配置文件
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
enabled: true
routes: # 路由數(shù)組[路由 就是指定當(dāng)請求滿足什么條件的時候轉(zhuǎn)到哪個微服務(wù)]
- id: product_route # 當(dāng)前路由的標(biāo)識, 要求唯一
uri: http://localhost:8081 # 請求要轉(zhuǎn)發(fā)到的地址
order: 1 # 路由的優(yōu)先級,數(shù)字越小級別越高
predicates: # 斷言(就是路由轉(zhuǎn)發(fā)要滿足的條件)
- Path=/product-serv/** # 當(dāng)請求路徑滿足Path指定的規(guī)則時,才進(jìn)行路由轉(zhuǎn)發(fā)
filters: # 過濾器,請求在傳遞過程中可以通過過濾器對其進(jìn)行一定的修改
- StripPrefix=1 # 轉(zhuǎn)發(fā)之前去掉1層路徑 |
4)測試
5.3.3 簡寫版
1)去掉關(guān)于路由的配置
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
2)啟動項目痰腮,并通過網(wǎng)關(guān)去訪問微服務(wù)
這時候而芥,就發(fā)現(xiàn)只要按照網(wǎng)關(guān)地址/微服務(wù)/接口的格式去訪問,就可以得到成功響應(yīng)膀值。
5.4 Gateway 核心架構(gòu)
5.4.1 基本概念
路由(Route) 是 gateway 中最基本的組件之一棍丐,表示一個具體的路由信息載體。主要定義了下面的幾個信息:
- id:路由標(biāo)識符沧踏,區(qū)別于其他 Route
- uri:路由指向的目的地 uri歌逢,即客戶端請求最終被轉(zhuǎn)發(fā)到的微服務(wù)。
- order:用于多個 Route 之間的排序翘狱,數(shù)值越小排序越靠前趋翻,匹配優(yōu)先級越高。
- predicate:斷言的作用是進(jìn)行條件判斷盒蟆,只有斷言都返回真踏烙,才會真正的執(zhí)行路由。
- filter:過濾器用于修改請求和響應(yīng)信息历等。
5.4.2 執(zhí)行流程執(zhí)行流程大體如下:
1)Gateway Client 向 Gateway Server 發(fā)送請求
2)請求首先會被HttpWebHandlerAdapter進(jìn)行提取組裝成網(wǎng)關(guān)上下文
3)然后網(wǎng)關(guān)的上下文會傳遞到 DispatcherHandler讨惩,它負(fù)責(zé)將請求分發(fā)給 RoutePredicateHandlerMapping
4)RoutePredicateHandlerMapping負(fù)責(zé)路由查找,并根據(jù)路由斷言判斷路由是否可用
5)如果過斷言成功寒屯,由FilteringWebHandler創(chuàng)建過濾器鏈并調(diào)用
6)請求會一次經(jīng)過PreFilter–微服務(wù)–PostFilter的方法荐捻,最終返回響應(yīng)
5.5 斷言
Predicate(斷言, 謂詞) 用于進(jìn)行條件判斷,只有斷言都返回真寡夹,才會真正的執(zhí)行路由
斷言就是說: 在 什么條件下 才能進(jìn)行路由轉(zhuǎn)發(fā)
5.5.1 內(nèi)置路由斷言工廠
Spring Cloud Gateway 包括許多內(nèi)置的斷言工廠处面,所有這些斷言都與HTTP請求的不同屬性匹配。具體如下:
基于Datetime類型的斷言工廠
此類型的斷言根據(jù)時間做判斷菩掏,主要有三個:
1. AfterRoutePredicateFactory: 接收一個日期參數(shù)魂角,判斷請求日期是否晚于指定日期
2. BeforeRoutePredicateFactory: 接收一個日期參數(shù),判斷請求日期是否早于指定日期
3. BetweenRoutePredicateFactory : 接收兩個日期參數(shù)智绸,判斷請求日期是否在指定時間段內(nèi)-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
基于遠(yuǎn)程地址的斷言工廠
RemoteAddrRoutePredicateFactory: 接收一個IP地址段野揪,判斷請求主機地址是否在地址段中
-RemoteAddr=192.168.1.1/24基于Cookie的斷言工廠
CookieRoutePredicateFactory:接收兩個參數(shù),cookie 名字和一個正則表達(dá)式瞧栗。 判斷請求
cookie是否具有給定名稱且值與正則表達(dá)式匹配斯稳。
-Cookie=chocolate, ch.基于Header的斷言工廠
HeaderRoutePredicateFactory:接收兩個參數(shù),標(biāo)題名稱和正則表達(dá)式迹恐。 判斷請求Header是否具有給定名稱且值與正則表達(dá)式匹配挣惰。
-Header=X-Request-Id, \d+基于Host的斷言工廠
HostRoutePredicateFactory:接收一個參數(shù),主機名模式。判斷請求的Host是否滿足匹配規(guī)則憎茂。
-Host=.testhost.org**基于Method請求方法的斷言工廠
MethodRoutePredicateFactory:接收一個參數(shù)唆涝,判斷請求類型是否跟指定的類型匹配。
-Method=GET基于Path請求路徑的斷言工廠
PathRoutePredicateFactory:接收一個參數(shù)唇辨,判斷請求的URI部分是否滿足路徑規(guī)則。
-Path=/foo/{segment}基于Query請求參數(shù)的斷言工廠
QueryRoutePredicateFactory :接收兩個參數(shù)能耻,請求param和正則表達(dá)式赏枚, 判斷請求參數(shù)是否具有給定名稱且值與正則表達(dá)式匹配。
-Query=baz, ba.基于路由權(quán)重的斷言工廠
WeightRoutePredicateFactory:接收一個[組名,權(quán)重], 然后對于同一個組內(nèi)的路由按照權(quán)重轉(zhuǎn)發(fā)
routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/
-Weight= group3, 9
內(nèi)置路由斷言工廠的使用
接下來我們驗證幾個內(nèi)置斷言的使用:
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: product_route
uri: lb://service-product
predicates:
- Path=/product-serv/**
- Before=2020-03-28T00:00:00.000+08:00 #限制請求時間在2019-03-28之前
- Method=POST #限制請求方式為POST
filters:
- StripPrefix=1
5.5.2 自定義路由斷言工廠
條件 :我們來設(shè)定一個場景: 假設(shè)我們的應(yīng)用僅僅讓age在(min,max)之間的人來訪問晓猛。
1)配置 bootstrap.yml
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: product-route
uri: lb://product-service
predicates:
- Path=/product-serv/**
- Age=18,60 #
filters:
- StripPrefix=1
2)自定義一個斷言工廠, 實現(xiàn)斷言方法
/**
* @Author Morse
* @Date 2020-04-17 17:20
*
* 這是一個自定義的路由斷言工廠類饿幅,要求有兩個
* 1. 名字必須是 配置 + RoutePredicateFactory
* 2. 必須繼承 AbstractRoutePredicateFactory<配置類>
*/
@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> {
// 構(gòu)造函數(shù)
public AgeRoutePredicateFactory() {
super(AgeRoutePredicateFactory.Config.class);
}
// 讀取配置文件中的參數(shù)值,給他賦值到配置類中的屬性上
@Override
public List<String> shortcutFieldOrder() {
// 這個位置的順序必須跟配置文件中的值的順序?qū)?yīng)
return Arrays.asList("minAge", "maxAge");
}
// 斷言邏輯
@Override
public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange exchange) {
// 1. 接收前臺傳入的age參數(shù)
String ageStr = exchange.getRequest().getQueryParams().getFirst("age");
// 2. 先判斷是否為空
if (!StringUtils.isEmpty(ageStr)) {
// 3. 如果不為空戒职,再進(jìn)行路由邏輯判斷
int age = Integer.parseInt(ageStr);
if (age < config.getMaxAge() && age > config.getMinAge()) {
return true;
} else {
return false;
}
}
return false;
}
};
}
@ApiModel(value = "配置類栗恩,用于接收配置文件中的對應(yīng)參數(shù)")
@Data
@NoArgsConstructor
public static class Config {
private int minAge; //18
private int maxAge; //60
}
}
4)啟動測試
測試發(fā)現(xiàn)當(dāng)age在(20,60)可以訪問,其它范圍不能訪問
http://localhost:7000/product-serv/product/1?age=30
http://localhost:7000/product-serv/product/1?age=10
5.6 過濾器
三個知識點:
1. 作用:過濾器就是在請求的傳遞過程中,對請求和響應(yīng)做一些手腳
2. 生命周期:Pre Post
3 分類:局部過濾器(作用在某一個路由上) 全局過濾器(作用全部路由上)
在 Gateway 中, Filter 的生命周期只有兩個:“pre” 和 “post”。
PRE:這種過濾器在請求被路由之前調(diào)用洪燥。我們可利用這種過濾器實現(xiàn)身份驗證磕秤、在集群中選擇請求的微服務(wù)、記錄調(diào)試信息等捧韵。
POST:這種過濾器在路由到微服務(wù)以后執(zhí)行市咆。這種過濾器可用來為響應(yīng)添加標(biāo)準(zhǔn)的 HTTP Header、收集統(tǒng)計信息和指標(biāo)再来、將響應(yīng)從微服務(wù)發(fā)送給客戶端等蒙兰。Gateway 的 Filter 從作用范圍可分為兩種:
GatewayFilter:應(yīng)用到單個路由或者一個分組的路由上。
GlobalFilter:應(yīng)用到所有的路由上芒篷。
5.6.1 局部過濾器
局部過濾器是針對單個路由的過濾器搜变。
5.6.1.1 內(nèi)置局部過濾器
在 Spring Cloud Gateway 中內(nèi)置了很多不同類型的網(wǎng)關(guān)路由過濾器。具體如下:
相關(guān)鏈接
序號 | 過濾器工廠 | 作用 | 參數(shù) |
---|---|---|---|
1 | AddRequestHeader | 為原始請求添加Header | Header的名稱與值 |
2 | AddRequestParameter | 為原始請求添加請求參數(shù) | 參數(shù)名稱及值 |
3 | AddResponseHeader | 為原始響應(yīng)添加Header | Header的名稱及值 |
4 | DedupeResponseHeader | 剔除響應(yīng)頭中重復(fù)的值 | 需要去重的Header名稱及去重策略 |
5 | Hystrix | 為路由引入Hystrix的斷路器保護(hù) | HystrixCommand的名稱 |
6 | CircuitBreaker | ||
7 | FallbackHeaders | 為fallbackUrl的請求頭中添加具體的異常信息 | Header的名稱 |
8 | MapRequestHeader | ||
9 | PrefixPath | 為原始請求路徑添加前綴 | 前綴路徑 |
10 | PreserveHostHeader | 為請求添加一個 preserveHostHeader=true 的屬性针炉,路由過濾器會檢查該屬性以決定是否要發(fā)送原始的Host | 無 |
11 | RequestRateLimiter | 用于對請求限流挠他,限流算法為令牌桶 | keyResolver、rateLimiter篡帕、statusCode绩社、denyEmptyKey、emptyKeyStatus |
12 | RedirectTo | 將原始請求重定向到指定的URL | http狀態(tài)碼及重定向的url |
13 | RemoveResponseHeader | 為原始請求刪除某個Header | Header名稱 |
14 | RemoveResponseHeader | 為原始響應(yīng)刪除某個Header | Header名稱 |
15 | RemoveRequestParameter | 為原始請求刪除某個Parameter | Parameter名稱 |
16 | RewritePath | 重寫原始的請求路徑 | 原始路徑正則表達(dá)以及重寫路徑的正則表達(dá)式 |
17 | RewriteLocationResponseHeader | ||
18 | RewriteResponseHeader | 重寫原始響應(yīng)中的某個Header | Header名稱赂苗,值的正則表達(dá)式愉耙,重寫后的值 |
19 | SaveSession | 在轉(zhuǎn)發(fā)請求之前,強制執(zhí)行 WebSession::save 操作 | 無 |
20 | SecureHeaders | 為原始響應(yīng)添加一系列起安全作用的響應(yīng)頭 | 無拌滋,支持修改這些安全響應(yīng)頭的值 |
21 | SetPath | 修改原始的請求路徑 | 修改后的路徑 |
22 | SetRequestHeader | 修改原始請求中某個Header的值 | Header名稱朴沿,修改后的值 |
23 | SetResponseHeader | 修改原始響應(yīng)中某個Header的值 | Header名稱,修改后的值 |
24 | SetStatus | 修改原始響應(yīng)的狀態(tài)碼 | HTTP狀態(tài)碼,可以是數(shù)字赌渣,也可以是字符串 |
25 | StripPrefix | 用于階段原始請求的路徑 | 使用數(shù)字表示要截斷的路徑的數(shù)量 |
26 | Retry | 針對不同的響應(yīng)進(jìn)行重試 | retries魏铅、statuses、methods坚芜、series览芳、exceptions、backoff |
27 | RequestSize | 設(shè)置允許接收最大請求包的大小鸿竖。如果請求包大小超過設(shè)置的值沧竟,則返回 413 Payload Too Large | 請求包大小,單位為字節(jié)缚忧,默認(rèn)值為5M |
28 | ModifyRequestBody | 在轉(zhuǎn)發(fā)請求之前修改原始請求體內(nèi)容 | 修改后的請求體內(nèi)容 |
29 | ModifyResponseBody | 修改原始響應(yīng)體的內(nèi)容 | 修改后的響應(yīng)體內(nèi)容 |
30 | Default | 為所有路由添加過濾器 | 過濾器工廠名稱及值 |
內(nèi)置局部過濾器的使用
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: product-route
uri: lb://product-service
predicates:
- Path=/product-serv/**
- Age=18,60 #
filters:
- StripPrefix=1
- SetStatus=2000 # 修改返回狀態(tài)
5.6.1.2 自定義局部過濾器
1)在配置文件中,添加一個Log的過濾器配置
server:
port: 7000
spring:
application:
name: api-gateway-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
routes:
- id: consumer
uri: lb://consumer
predicates:
- Path=/consumer-serv/**
- Age=18,60 #
filters:
- StripPrefix=1
- Log=true,false # 修改返回狀態(tài)
2)自定義一個過濾器工廠,實現(xiàn)方法
@Slf4j
@Component
public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory<LogGatewayFilterFactory.Config>
//構(gòu)造函數(shù)
public LogGatewayFilterFactory() {
super(LogGatewayFilterFactory.Config.class);
}
//讀取配置文件中的參數(shù) 賦值到 配置類中
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("consoleLog", "cacheLog");
}
//過濾器邏輯
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.isCacheLog()) {
System.out.println("cacheLog已經(jīng)開啟了......");
}
if (config.isConsoleLog()) {
System.out.println("consoleLog已經(jīng)開啟了......");
}
return chain.filter(exchange);
}
};
}
// 配置類 接收配置參數(shù)
@Data
@NoArgsConstructor
public static class Config {
private boolean consoleLog;
private boolean cacheLog;
}
}
3)啟動測試
5.6.2 全局過濾器
全局過濾器作用于所有路由, 無需配置悟泵。通過全局過濾器可以實現(xiàn)對權(quán)限的統(tǒng)一校驗,安全性驗證等功能闪水。
5.6.2.1 內(nèi)置全局過濾器
Spring Cloud Gateway 內(nèi)部也是通過一系列的內(nèi)置全局過濾器對整個路由轉(zhuǎn)發(fā)進(jìn)行處理如下:
5.6.2.2 自定義全局過濾器
內(nèi)置的過濾器已經(jīng)可以完成大部分的功能糕非,但是對于企業(yè)開發(fā)的一些業(yè)務(wù)功能處理,還是需要我們自己編寫過濾器來實現(xiàn)的球榆,那么我們一起通過代碼的形式自定義一個過濾器朽肥,去完成統(tǒng)一的權(quán)限校驗。
開發(fā)中的鑒權(quán)邏輯:
1)當(dāng)客戶端第一次請求服務(wù)時持钉,服務(wù)端對用戶進(jìn)行信息認(rèn)證(登錄)
2)認(rèn)證通過鞠呈,將用戶信息進(jìn)行加密形成token,返回給客戶端右钾,作為登錄憑證
3)以后每次請求蚁吝,客戶端都攜帶認(rèn)證的token
4)服務(wù)端對token進(jìn)行解密,判斷是否有效舀射。
如上圖窘茁,對于驗證用戶是否已經(jīng)登錄鑒權(quán)的過程可以在網(wǎng)關(guān)統(tǒng)一檢驗。檢驗的標(biāo)準(zhǔn)就是請求中是否攜帶token憑證以及token的正確性脆烟。
下面的我們自定義一個GlobalFilter山林,去校驗所有請求的請求參數(shù)中是否包含token,如何不包含請求參數(shù)token則不轉(zhuǎn)發(fā)路由邢羔,否則執(zhí)行正常的邏輯驼抹。
/**
* @Author Morse
* @Date 2020-04-18 09:51
*
* 自定義全局過濾器需要實現(xiàn) GlobalFilter 和 Ordered 接口
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
// 完成判斷邏輯
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (!StringUtils.equals( token, "admin")) {
System.out.println("健全失敗");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 調(diào)用chain.filter繼續(xù)向下游執(zhí)行
return chain.filter(exchange);
}
// 順序,數(shù)值越小拜鹤,優(yōu)先級越高
@Override
public int getOrder() {
return 0;
}
}
5.7 網(wǎng)關(guān)限流
網(wǎng)關(guān)是所有請求的公共入口框冀,所以可以在網(wǎng)關(guān)進(jìn)行限流,而且限流的方式也很多敏簿,我們本次采用前面學(xué)過的Sentinel組件來實現(xiàn)網(wǎng)關(guān)的限流明也。Sentinel支持對Spring Cloud Gateway宣虾、Zuul等主流網(wǎng)關(guān)進(jìn)行限流。
從1.6.0版本開始温数,Sentinel提供了Spring Cloud Gateway的適配模塊绣硝,可以提供兩種資源維度的限流:
route維度:即在Spring配置文件中配置的路由條目,資源名為對應(yīng)的routeId
自定義API維度:用戶可以利用Sentinel提供的API來自定義一些API分組
1)導(dǎo)入依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
2)編寫配置類
基于 Sentinel 的 Gateway 限流是通過其提供的 Filter 來完成的撑刺,使用時只需注入對應(yīng)的 SentinelGatewayFilter 實例以及 SentinelGatewayBlockExceptionHandler 實例即可鹉胖。
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
// 初始化一個限流的過濾器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
// 配置初始化的限流參數(shù)
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
// 資源名稱,對應(yīng)路由ID
new GatewayFlowRule("product-route")
// 限流閥值
.setCount(1)
// 統(tǒng)計時間窗口够傍,單位是秒甫菠,默認(rèn)是1秒
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的異常處理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
// 自定義限流異常頁面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
3)測試
在一秒鐘內(nèi)多次訪問http://localhost:7000/product-serv/product/1就可以看到限流啟作用了。
4)自定義API分組
自定義API分組是一種更細(xì)粒度的限流規(guī)則定義
// 配置初始化的限流參數(shù)
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
// 資源名稱王带,對應(yīng)路由ID
new GatewayFlowRule("product-route")
// 限流閥值
.setCount(1)
// 統(tǒng)計時間窗口,單位是秒市殷,默認(rèn)是1秒
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("product-api1").setCount(1) .setIntervalSec(1));
rules.add(new GatewayFlowRule("product-api2").setCount(1) .setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
// 自定義API分組
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("product-api1")
.setPredicateItems(
new HashSet<ApiPredicateItem>() {{
// 以/product-serv/product/api1 開頭的請求
add(
new ApiPathPredicateItem()
.setPattern("/product-serv/product/api1/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)
);
}}
);
ApiDefinition api2 = new ApiDefinition("product-api2")
.setPredicateItems(
new HashSet<ApiPredicateItem>(){{
add( new ApiPathPredicateItem().setPattern("/product-serv/product/api2/demo1"));
}}
);
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}