自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

16 圖 | 深入理解 Spring Cloud Gateway 的原理

開發(fā) 前端
在 PassJava 項目中,我用到了 Spring Cloud Gateway 作為 API 網(wǎng)關(guān),客戶端的所有的請求都是先經(jīng)過網(wǎng)關(guān),然后再轉(zhuǎn)發(fā)到會員微服務(wù)、題目微服務(wù)等。

你好,我是悟空。

本篇給大家?guī)淼氖俏⒎?wù)框架中非常重要的一個組件:API 網(wǎng)關(guān)。

圖片

前言

在 PassJava 項目中,我用到了 Spring Cloud Gateway 作為 API 網(wǎng)關(guān),客戶端的所有的請求都是先經(jīng)過網(wǎng)關(guān),然后再轉(zhuǎn)發(fā)到會員微服務(wù)、題目微服務(wù)等。

比如 API 網(wǎng)關(guān)和會員微服務(wù)對應(yīng)的訪問地址如下:

API 網(wǎng)關(guān)地址:http://localhost:8060

會員微服務(wù)地址:http://localhost:14000

客戶端請求都是訪問的 API 網(wǎng)關(guān),然后網(wǎng)關(guān)轉(zhuǎn)發(fā)到會員微服務(wù),客戶端無需知道會員微服務(wù)的地址。

本篇將會以 PassJava 作為案例進行講解。

PassJava 開源地址:https://github.com/Jackson0714/PassJava-Platform

為什么需要 API 網(wǎng)關(guān)

在 SpringBoot 單體架構(gòu)中,一般只有一個后端服務(wù),如下圖所示:

圖片

單體架構(gòu)訪問示例圖

但是在 SpringCloud 微服務(wù)架構(gòu)中,往往有多個微服務(wù),這些微服務(wù)可能部署在不同的機器上,而且一個微服務(wù)可能會擴容成多個相同的微服務(wù),組成微服務(wù)集群。

圖片

微服務(wù)架構(gòu)訪問示例圖

這種情況下,會存在如下問題:

  • 如果需要添加鑒權(quán)功能,則需要對每個微服務(wù)進行改造。
  • 如果需要對流量進行控制,則需要對每個微服務(wù)進行改造。
  • 跨域問題,需要對每個微服務(wù)進行改造。
  • 存在安全問題,每個微服務(wù)需要暴露自己的 Endpoint 給客戶端。Endpoint 就是服務(wù)的訪問地址 + 端口。比如下面的地址:
http://order.passjava.cn:8000
  • 灰度發(fā)布、動態(tài)路由需要對每個微服務(wù)進行改造。

這個問題的痛點是各個微服務(wù)都是一個入口,有沒有辦法統(tǒng)一入口呢?

解決這個問題的方式就是在客戶端和服務(wù)器之間加個中間商就好了呀,只有中間商一個入口,這個中間商就是網(wǎng)關(guān)。

還有一個細節(jié)問題:多個微服務(wù)之間是如何通信的?這就要用到遠程調(diào)用組件了,比如 OpenFeign。但是服務(wù)之間的調(diào)用是需要知道對方的 Endpoint 的,如果一個服務(wù)有多個微服務(wù),就需要通過負載均衡組件進行流量分發(fā)。那微服務(wù)之間不就暴露 Endpoint 了嗎?這個沒有問題,畢竟只是后端服務(wù)知道,外界是不知道的。

圖片

為了幫助大家更容易理解網(wǎng)關(guān)的作用,這里有個網(wǎng)關(guān)、客戶端、微服務(wù)的三方通話。

網(wǎng)關(guān)對話

網(wǎng)關(guān):客戶端你好,你現(xiàn)在可以只跟我通信了,我可以將你本來想發(fā)給微服務(wù)的流量進行轉(zhuǎn)發(fā),微服務(wù)處理完之后,將結(jié)果返回給我,我再給你。

客戶端:你沒有賺差價吧?

API 網(wǎng)關(guān):我可能會加些請求頭、做下認證、鑒權(quán)、限流等。

客戶端:微服務(wù)不是自己可以做嗎?

API 網(wǎng)關(guān):但是每個微服務(wù)都得自己加,這就很麻煩了,都交給我就好了。

微服務(wù):網(wǎng)關(guān)你好,你會為我保密我的地址對嗎?

API 網(wǎng)關(guān):當然,我給客戶端看的是我自己的地址,客戶端不需要知道你的地址,只需要知道你的 API 是哪個就行,剩下的交給我來轉(zhuǎn)發(fā)給你。

API 網(wǎng)關(guān)選型對比

業(yè)界比較出名的網(wǎng)關(guān):Spring Cloud Gateway、Netflix Zuul、Nginx、Kong、Alibaba Tengine。

作為 Spring Cloud 全家桶中的一款組件,當然選擇 Spring Cloud Gateway 了。

最開始 Spring Cloud 推薦的網(wǎng)關(guān)是 Netflix Zuul 1.x,但是停止維護了,后來又有 Zuul 2.0,但是因為開發(fā)延期比較嚴重,Spring Cloud 官方自己開發(fā)了 Spring Cloud Gateway 網(wǎng)關(guān)組件,用于代替 Zuul 網(wǎng)關(guān)。

所以本篇我們只會講解 Spring Cloud Gateway 網(wǎng)關(guān)組件。

Spring Cloud Gateway 的工作流程

Gateway 的工作流程如下圖所示:

圖片

① 路由判斷;客戶端的請求到達網(wǎng)關(guān)后,先經(jīng)過 Gateway Handler Mapping 處理,這里面會做斷言(Predicate)判斷,看下符合哪個路由規(guī)則,這個路由映射后端的某個服務(wù)。

② 請求過濾:然后請求到達 Gateway Web Handler,這里面有很多過濾器,組成過濾器鏈(Filter Chain),這些過濾器可以對請求進行攔截和修改,比如添加請求頭、參數(shù)校驗等等,有點像凈化污水。然后將請求轉(zhuǎn)發(fā)到實際的后端服務(wù)。這些過濾器邏輯上可以稱作 Pre-Filters,Pre 可以理解為“在...之前”。

③ 服務(wù)處理:后端服務(wù)會對請求進行處理。

④ 響應(yīng)過濾:后端處理完結(jié)果后,返回給 Gateway 的過濾器再次做處理,邏輯上可以稱作 Post-Filters,Post 可以理解為“在...之后”。

⑤ 響應(yīng)返回:響應(yīng)經(jīng)過過濾處理后,返回給客戶端。

小結(jié):客戶端的請求先通過匹配規(guī)則找到合適的路由,就能映射到具體的服務(wù)。然后請求經(jīng)過過濾器處理后轉(zhuǎn)發(fā)給具體的服務(wù),服務(wù)處理后,再次經(jīng)過過濾器處理,最后返回給客戶端。

Spring Cloud Gateway 的斷言

斷言(Predicate)這個詞聽起來極其深奧,它是一種編程術(shù)語,我們生活中根本就不會用它。說白了它就是對一個表達式進行 if 判斷,結(jié)果為真或假,如果為真則做這件事,否則做那件事。

在 Gateway 中,如果客戶端發(fā)送的請求滿足了斷言的條件,則映射到指定的路由器,就能轉(zhuǎn)發(fā)到指定的服務(wù)上進行處理。

斷言配置的示例如下,配置了兩個路由規(guī)則,有一個 predicates 斷言配置,當請求 url 中包含 api/thirdparty,就匹配到了第一個路由 route_thirdparty。(代碼示例來自我的開源項目 PassJava)。

圖片

圖片斷言配置

接下來我們看下 Route 路由和 Predicate 斷言的對應(yīng)關(guān)系:

圖片

斷言和路由的對應(yīng)關(guān)系原理圖

  • 一對多:一個路由規(guī)則可以包含多個斷言。如上圖中路由 Route1 配置了三個斷言 Predicate。
  • 同時滿足:如果一個路由規(guī)則中有多個斷言,則需要同時滿足才能匹配。如上圖中路由 Route2 配置了兩個斷言,客戶端發(fā)送的請求必須同時滿足這兩個斷言,才能匹配路由 Route2。
  • 第一個匹配成功:如果一個請求可以匹配多個路由,則映射第一個匹配成功的路由。如上圖所示,客戶端發(fā)送的請求滿足 Route3 和 Route4 的斷言,但是 Route3 的配置在配置文件中靠前,所以只會匹配 Route3。

常見的 Predicate 斷言配置如下所示,假設(shè)匹配路由成功后,轉(zhuǎn)發(fā)到 http://localhost:9001

圖片

圖片常見的 Predicate 斷言配置

代碼演示

下面演示 Gateway 中通過斷言來匹配路由的例子。

  • 新建一個 Maven 工程,引入 Gateway 依賴。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
  • 新建 application.yml 文件,添加 Gateway 的路由規(guī)則。
spring:
cloud:
gateway:
routes:
- id: route_qq
uri: http://www.qq.com
predicates:
- Query=url,qq
- id: route_baidu
uri: http://www.baidu.com
predicates:
- Query=url,baidu
server:
port: 8060

第一條路由規(guī)則:斷言為 Query=url,qq,表示當請求路徑中包含 url=qq,則跳轉(zhuǎn)到http://www.qq.com

第二條路由規(guī)則:當請求路徑中包含 url=baidu,則跳轉(zhuǎn)到http://www.baidu.com

Spring Cloud Gateway 動態(tài)路由

在微服務(wù)架構(gòu)中,我們不會直接通過 IP + 端口的方式訪問微服務(wù),而是通過服務(wù)名的方式來訪問。如下圖所示,微服務(wù)中加入了注冊中心,多個微服務(wù)將自己注冊到了注冊中心,這樣注冊中心就保存了服務(wù)名和 IP+端口的映射關(guān)系。

圖片

圖片微服務(wù)注冊到注冊中心

接下來我們來看下加入 Gateway 后,請求是如何進行轉(zhuǎn)發(fā)的。

客戶端先將請求發(fā)送給 Nginx,然后轉(zhuǎn)發(fā)到網(wǎng)關(guān),網(wǎng)關(guān)經(jīng)過斷言匹配到一個路由后,將請求轉(zhuǎn)發(fā)給指定 uri,這個 uri 可以配置成 微服務(wù)的名字,比如 passjava-member。

那么這個服務(wù)名具體要轉(zhuǎn)發(fā)到哪個 IP 地址和端口上呢?這個就依賴注冊中心的注冊表了,Gateway 從注冊中心拉取注冊表,就能知道服務(wù)名對應(yīng)具體的 IP + 端口,如果一個服務(wù)部署了多臺機器,則還可以通過負載均衡進行請求的轉(zhuǎn)發(fā)。原理如下圖所示:

圖片

網(wǎng)關(guān)+注冊中心

對應(yīng)的配置為 uri 字段如下所示:

圖片

圖片uri: lb://passjava-question,表示將請求轉(zhuǎn)發(fā)給 passjava-question 微服務(wù),且支持負載均衡。lb 是 loadbalance(負載均衡) 單詞的縮寫。

那什么叫動態(tài)路由呢?

當 passjava-question 服務(wù)添加一個微服務(wù),或者 IP 地址更換了,Gateway 都是可以感知到的,但是配置是不需要更新的。這里的動態(tài)指的是微服務(wù)的集群個數(shù)、IP 和端口是動態(tài)可變的。

代碼示例

案例:調(diào)用 OSS 第三方服務(wù),上傳文件到 OSS。(基于 PassJava 項目)

前提:前端頁面配置的統(tǒng)一訪問路徑是網(wǎng)關(guān)的地址:http://localhost:8060/api/,OSS 服務(wù)對應(yīng)的地址是http://localhost:14000。

期望結(jié)果:將前端請求

http://localhost:8060/api/thirdparty/v1/admin/oss/getPolicy

轉(zhuǎn)發(fā)到 OSS 服務(wù)。

http://localhost:14000/thirdparty/v1/admin/oss/getPolicy

配置網(wǎng)關(guān):

spring:
cloud:
gateway:
routes:
- id: route_thirdparty # 第三方微服務(wù)路由規(guī)則
uri: lb://passjava-thirdparty # 負載均衡,將請求轉(zhuǎn)發(fā)到注冊中心注冊的 passjava-thirdparty 服務(wù)
predicates: # 斷言
- Path=/api/thirdparty/** # 如果前端請求路徑包含 api/thirdparty,則應(yīng)用這條路由規(guī)則
filters: #過濾器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 將跳轉(zhuǎn)路徑中包含的api替換成空

測試上傳文件成功。

圖片

圖片

接下來我們看下 Gateway 非常重要且核心的功能:過濾器。

Spring Cloud Gateway 的過濾器

網(wǎng)關(guān),顧名思義,就是網(wǎng)絡(luò)中的一道關(guān)卡,可以統(tǒng)一對請求和響應(yīng)進行一些操作。

過濾器 Filter 的分類

過濾器 Filter 按照請求和響應(yīng)可以分為兩種:Pre 類型和 Post 類型。

Pre 類型:在請求被轉(zhuǎn)發(fā)到微服務(wù)之前,對請求進行攔截和修改,例如參數(shù)校驗、權(quán)限校驗、流量監(jiān)控、日志輸出以及協(xié)議轉(zhuǎn)換等操作。

Post 類型:微服務(wù)處理完請求后,返回響應(yīng)給網(wǎng)關(guān),網(wǎng)關(guān)可以再次進行處理,例如修改響應(yīng)內(nèi)容或響應(yīng)頭、日志輸出、流量監(jiān)控等。

另外一種分類是按照過濾器 Filter 作用的范圍進行劃分:

GlobalFilter:全局過濾器,應(yīng)用在所有路由上的過濾器。

局部過濾器

GatewayFilter:局部過濾器,應(yīng)用在單個路由或一組路由上的過濾器。標紅色表示比較常用的過濾器。

整理了一份 27 種自帶的 GatwayFilter 過濾器。

圖片

具體怎么用呢,這里有個示例,如果 URL 匹配成功,則去掉 URL 中的 “api”。

filters: #過濾器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 將跳轉(zhuǎn)路徑中包含的 “api” 替換成空

當然我們也可以自定義過濾器,本篇不做展開。

全局過濾器

整理了一份全局過濾器的表格,具體用法可以參照官方文檔。

官方文檔:https://cloud.spring.io/spring-cloud-static/Greenwich.SR2/single/spring-cloud.html#_global_filters

圖片

全局過濾器最常見的用法是進行負載均衡。配置如下所示:

spring:
cloud:
gateway:
routes:
- id: route_member # 第三方微服務(wù)路由規(guī)則
uri: lb://passjava-member # 負載均衡,將請求轉(zhuǎn)發(fā)到注冊中心注冊的 passjava-member 服務(wù)
predicates: # 斷言
- Path=/api/member/** # 如果前端請求路徑包含 api/member,則應(yīng)用這條路由規(guī)則
filters: #過濾器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 將跳轉(zhuǎn)路徑中包含的api替換成空

這里有個關(guān)鍵字 lb,用到了全局過濾器 LoadBalancerClientFilter,當匹配到這個路由后,會將請求轉(zhuǎn)發(fā)到 passjava-member 服務(wù),且支持負載均衡轉(zhuǎn)發(fā),也就是先將 passjava-member 解析成實際的微服務(wù)的 host 和 port,然后再轉(zhuǎn)發(fā)給實際的微服務(wù)。

實現(xiàn)簡單的 token 認證

在用 Gateway 做登錄認證的時候,通常需要我們自定義一個過濾器做登錄認證。

比如客戶端登錄時,將用戶名和密碼發(fā)送給網(wǎng)關(guān),網(wǎng)關(guān)轉(zhuǎn)發(fā)給認證服務(wù)器后,如果賬號密碼正確,則拿到一個 JWT token,然后客戶端再訪問應(yīng)用服務(wù)時,先將請求發(fā)送給網(wǎng)關(guān),網(wǎng)關(guān)統(tǒng)一做 JWT 認證,如果 JWT 符合條件,再將請求轉(zhuǎn)發(fā)給應(yīng)用服務(wù)。

原理如下圖所示,紅色框框的部分就是待會我要演示的部分。

圖片

案例演示

下面做一個簡單的認證實例??蛻舳藬y帶 token 訪問 member 服務(wù),網(wǎng)關(guān)會先校驗 token 的合法性,驗證規(guī)則如下:

當請求的 header 中包含 token,且 token = admin,則認證通過。

當驗證通過后,就會將請求轉(zhuǎn)發(fā)給 member 服務(wù)。

代碼示例

先定義一個全局過濾器,驗證 token 的合法性。

@Component
public class GlobalLoginFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request= exchange.getRequest();
String token = request.getHeaders().getFirst("token");
if(!StringUtils.isEmpty(token)){
if("admin".equals(token)){
return chain.filter(exchange);
}
}
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}

@Override
public int getOrder() {
return 0;
}
}
  • 測試 token 不正確的場景。

先測試在 header 中添加 token=123,響應(yīng)結(jié)果為 401  Unauthorized,沒有權(quán)限。

圖片

  • 測試 token 正確的場景。

然后測試在 header 中添加 token=admin,正常返回響應(yīng)數(shù)據(jù)。


圖片


責任編輯:武曉燕 來源: 悟空聊架構(gòu)
相關(guān)推薦

2021-03-10 10:55:51

SpringJava代碼

2022-08-22 08:04:25

Spring事務(wù)Atomicity

2022-01-14 12:28:18

架構(gòu)OpenFeign遠程

2022-11-04 09:43:05

Java線程

2024-03-12 00:00:00

Sora技術(shù)數(shù)據(jù)

2022-09-05 08:39:04

kubernetesk8s

2024-11-01 08:57:07

2020-08-10 18:03:54

Cache存儲器CPU

2024-04-15 00:00:00

技術(shù)Attention架構(gòu)

2020-03-18 13:40:03

Spring事數(shù)據(jù)庫代碼

2023-06-07 15:34:21

架構(gòu)層次結(jié)構(gòu)

2021-05-27 11:30:54

SynchronizeJava代碼

2020-03-26 16:40:07

MySQL索引數(shù)據(jù)庫

2023-09-19 22:47:39

Java內(nèi)存

2022-09-26 08:01:31

線程LIFO操作方式

2023-10-13 13:30:00

MySQL鎖機制

2020-11-04 15:35:13

Golang內(nèi)存程序員

2019-07-01 13:34:22

vue系統(tǒng)數(shù)據(jù)

2022-09-05 22:22:00

Stream操作對象

2020-03-17 08:36:22

數(shù)據(jù)庫存儲Mysql
點贊
收藏

51CTO技術(shù)棧公眾號