點贊再看,養成習慣,微信搜尋【牧小農】關注我獲取更多資訊,風裡雨裡,小農等你,很高興能夠成為你的朋友。
專案原始碼地址:公眾號回覆 sentinel,即可免費獲取原始碼
在微服務架構中,通常一個系統會被拆分為多個微服務,面對這麼多微服務使用者端應該如何去呼叫呢?如果沒有其他更優方法,我們只能記錄每個微服務對應的地址,分別去呼叫,但是這樣會有很多的問題和潛在因素。
為了解決上面的問題,微服務引入了 閘道器 的概念,閘道器為微服務架構的系統提供簡單、有效且統一的API路由管理,作為系統的統一入口,提供內部服務的路由中轉,給使用者端提供統一的服務,可以實現一些和業務沒有耦合的公用邏輯,主要功能包含認證、鑑權、路由轉發、安全策略、防刷、流量控制、監控紀錄檔等。
閘道器在微服務中的位置:
Zuul 1.0 : Netflix開源的閘道器,使用Java開發,基於Servlet架構構建,便於二次開發。因為基於Servlet內部延遲嚴重,並行場景不友好,一個執行緒只能處理一次連線請求。
Zuul 2.0 : 採用Netty實現非同步非阻塞程式設計模型,一個CPU一個執行緒,能夠處理所有的請求和響應,請求響應的生命週期通過事件和回撥進行處理,減少執行緒數量,開銷較小
GateWay : 是Spring Cloud的一個全新的API閘道器專案,替換Zuul開發的閘道器服務,基於Spring5.0 + SpringBoot2.0 + WebFlux(基於⾼效能的Reactor模式響應式通訊框架Netty,非同步⾮阻塞模型)等技術開發,效能高於Zuul
Nginx+lua : 效能要比上面的強很多,使用Nginx的反向程式碼和負載均衡實現對API伺服器的負載均衡以及高可用,lua作為一款指令碼語言,可以編寫一些簡單的邏輯,但是無法嵌入到微服務架構中
Kong : 基於OpenResty(Nginx + Lua模組)編寫的高可用、易擴充套件的,效能高效且穩定,支援多個可用外掛(限流、鑑權)等,開箱即可用,只支援HTTP協定,且二次開發擴充套件難,缺乏更易用的管理和設定方式
官方檔案:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
Spring Cloud Gateway 是Spring Cloud的一個全新的API閘道器專案,目的是為了替換掉Zuul1,它基於Spring5.0 + SpringBoot2.0 + WebFlux(基於⾼效能的Reactor模式響應式通訊框架Netty,非同步⾮阻塞模型)等技術開發,效能⾼於Zuul,官⽅測試,Spring Cloud GateWay是Zuul的1.6倍 ,旨在為微服務架構提供⼀種簡單有效的統⼀的API路由管理⽅式。
可以與Spring Cloud Discovery Client(如Eureka)、Ribbon、Hystrix等元件配合使用,實現路由轉發、負載均衡、熔斷、鑑權、路徑重寫、⽇志監控等
Gateway還內建了限流過濾器,實現了限流的功能。
設計優雅,容易拓展
路由(Route)是GateWay中最基本的元件之一,表示一個具體的路由資訊載體,主要由下面幾個部分組成:
核心概念:
Gateway Client
向 Spring Cloud Gateway
傳送請求HttpWebHandlerAdapter
進行提取組裝成閘道器上下文DispatcherHandler
,它負責將請求分發給 RoutePredicateHandlerMapping
RoutePredicateHandlerMapping
負責路由查詢,並根據路由斷言判斷路由是否可用 FilteringWebHandler
建立過濾器鏈並呼叫Fliter
鏈執行請求,Filter
被虛線分隔的原因是Filter可以在傳送代理請求之前(pre)和之後(post)執行邏輯Response
返回到 Gateway
使用者端Filter過濾器:
當用戶發出請求達到 GateWay
之後,會通過一些匹配條件,定位到真正的服務節點,並且在這個轉發過程前後,進行一些細粒度的控制,其中 Predicate(斷言) 是我們的匹配條件,Filter 是一個攔截器,有了這兩點,再加上URL,就可以實現一個具體的路由,核心思想:路由轉發+執行過濾器鏈
這個過程就好比考試,我們考試首先要找到對應的考場,我們需要知道考場的地址和名稱(id和url),然後我們進入考場之前會有考官檢視我們的准考證是否匹配(斷言),如果匹配才會進入考場,我們進入考場之後,(路由之前)會進行身份的登記和考試的科目,填寫考試資訊,當我們考試完成之後(路由之後)會進行簽字交卷,走出考場,這個就類似我們的過濾器
Route(路由) :構建閘道器的基礎模組,由ID、目標URL、過濾器等組成
Predicate(斷言) :開發人員可以匹配HTTP請求中的內容(請求頭和請求引數),如果請求斷言匹配賊進行路由
Filter(過濾) :GateWayFilter的範例,使用過濾器,可以在請求被路由之前或者之後對請求進行修改
通過上述講解已經瞭解了基礎概念,我們來動手搭建一個GateWay
專案,來看看它到底是如何執行的
新建專案:cloud-alibaba-gateway-9006
版本對應:
GateWay屬於SprinigCloud且有web依賴,在我們匯入對應依賴時,要注意版本關係,我們這裡使用的版本是 2.2.x的版本,所以配合使用的Hoxton.SR5
版本
在這裡我們要注意的是引入GateWay一定要刪除spring-boot-starter-web依賴,否則會有衝突無法啟動
父類別pom參照:
<spring-cloud-gateway-varsion>Hoxton.SR5</spring-cloud-gateway-varsion>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud-gateway-varsion}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
子類POM參照:
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
yml設定
server:
port: 9006
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: false #開啟註冊中心路由功能
routes: # 路由
- id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
uri: http://localhost:9001/nacos-provider # 匹配提供服務的路由地址 lb://表示開啟負載均衡
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
我們在之前的cloud-alibaba-nacos-9001
專案中新增下面測試程式碼
@RestController
@RequestMapping("/mxn")
public class DemoController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/hello")
public String hello(){
return "hello world ,my port is :"+serverPort;
}
}
啟動Nacos、cloud-alibaba-nacos-9001
、cloud-alibaba-gateway-9006
通過gateway閘道器去存取9001的mxn/order看看。
首先我們在Nacos中看到我們服務是註冊到Nacos中了
然後我們存取http://localhost:9001/mxn/hello
,確保是成功的,在通過http://localhost:9006/mxn/hello
去存取,也是OK,說明我們GateWay搭建成功,我們進入下一步
在上述方法中我們是通過YML去完成的設定,GateWay
還提供了另外一種設定方式,就是通過程式碼的方式進行設定,@Bean
注入一個 RouteLocator
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GateWayConfig {
/*
設定了一個id為path_mxn的路由規則
當存取地址http://localhost:9999/mxn/**
就會轉發到http://localhost:9001/nacos-provider/mxn/任何地址
*/
@Bean
public RouteLocator gateWayConfigInfo(RouteLocatorBuilder routeLocatorBuilder){
// 構建多個路由routes
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
// 具體路由地址
routes.route("path_mxn",r -> r.path("/mxn/**").uri("http://localhost:9001/nacos-provider")).build();
// 返回所有路由規則
return routes.build();
}
}
我們可以將路由註釋掉之後看一下,重啟9006服務,存取地址http://localhost:9006/mxn/hello
就可以轉發到9001中具體的介面中
這裡並不推薦,使用程式碼的方式來進行設定gateWay
,大家有個瞭解就可以,因為程式碼的設定維護的成本比較高,而且對於一些需要修改的項,需要改程式碼才可以完成,這樣不利於維護和拓展,所以還是推薦大家使用yml進行設定。
在上述的講解中,我們已經掌握了 GateWay
的一些基本設定和兩種使用方式,下面我們就來講解一下 GateWay
如何實現負載均衡
我們只需要在9006中新增lb://nacos-provider
就可以顯示負載均衡。
當我們去存取http://localhost:9006/mxn/hello
的時候,就可以看到9001和9002不停的切換
在這一篇中我們來研究一下 斷言 ,我們可以理解為:當滿足條件後才會進行轉發路由,如果是多個,那麼多個條件需要同時滿足
在官方提供的斷言種類有11種(最新的有12種型別):
具體地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
1. After : 表示設定時間之後才進行轉發
時間戳獲取程式碼,用於時間程式碼的獲取:
public static void main(String[] args) {
ZonedDateTime zbj = ZonedDateTime.now();//預設時區
System.out.println(zbj);
}
spring:
application:
name: cloud-gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true #開啟註冊中心路由功能
routes: # 路由
- id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
uri: lb://nacos-provider # 匹配提供服務的路由地址 lb://表示開啟負載均衡
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
- After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
如果在時間段之前存取則404
Before
匹配ZonedDateTime
型別的時間,表示匹配在指定日期時間之前的請求,之後的請求則拒絕404錯誤
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
- Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
Between
Between
可以匹配ZonedDateTime
型別的時間,由兩個ZonedDateTime
引陣列成,第一個引數為開始時間,第二引數為結束時間,逗號進行分隔,匹配在指定的開始時間與結束時間之內的請求,設定如下:
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
- Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
Cookie
由兩個引陣列成,分別為name(Key)
和regexp(正規表示式)(Value
),匹配具有給定名稱且其值與正規表示式匹配的Cookie。
路由規則會通過獲取Cookie name值和正規表示式去匹配,如果匹配上就會執行路由,如果匹配不上則不執行。
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
- Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
小寫字母匹配成功:
數位匹配不成功:
Header
由兩個引陣列成,第一個引數為Header名稱
,第二引數為Header的Value值
,指定名稱的其值和正規表示式相匹配的Header的請求
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
- Header=headerName, \d+ # \d表示數位
請求頭攜帶數位斷言請求成功,
斷言字母匹配失敗:
Host
匹配當前請求是否來自於設定的主機。
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
# - Header=headerName, \d+ # \d表示數位
- Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
滿足Host斷言,請求成功
不滿足Host斷言失敗
**Method **
可以設定一個或多個引數,匹配HTTP請求,比如POST,PUT,GET,DELETE
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
# - Header=headerName, \d+ # \d表示數位
# - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
- Method=POST,GET
GET斷言成功
PUT斷言請求失敗
Query
由兩個引陣列成,第一個為引數名稱(必須),第二個為引數值(可選-正規表示式),匹配請求中是否包含第一個引數,如果有兩個引數,則匹配請求中第一個引數的值是否符合第二個正規表示式。
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
# - Header=headerName, \d+ # \d表示數位
# - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
# - Method=POST,GET
- Query=id,.+ # 匹配任意請求引數,這裡如果需要匹配多個引數,可以寫多個- Query=
斷言匹配 請求成功
RemoteAddr
引數由CIDR 表示法(IPv4 或 IPv6)字串組成,也就是匹配的ID地址,設定如下:
predicates: # 斷言
- Path=/mxn/** # 斷言,路徑相匹配進行路由
# - After=2022-06-11T16:30:40.785+08:00[Asia/Shanghai] #在這個時間之後的請求夠可以進行通過,之前的則不能進行存取
# - Before=2022-06-11T15:30:40.785+08:00[Asia/Shanghai]
# - Between=2022-06-11T15:30:40.785+08:00[Asia/Shanghai],2022-06-11T16:30:40.785+08:00[Asia/Shanghai]
# - Cookie=muxiaonong,[a-z]+ # 匹配Cookie的key和value(正規表示式)表示任意字母
# - Header=headerName, \d+ # \d表示數位
# - Host=**.muxiaonong.com #匹配當前的主機地址發出的請求
# - Method=POST,GET
# - Query=id,.+ # 匹配任意請求引數,這裡如果需要匹配多個引數,可以寫多個Query
- RemoteAddr=192.168.1.1/24
RemoteAddr
需要兩個引數group和weight(int)權重數值,實現了路由權重功能,表示將相同的請求根據權重跳轉到不同的uri地址,要求group的名稱必須一致
routes: # 路由
- id: weight_high #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
uri: https://blog.csdn.net/qq_14996421
predicates: # 斷言
- Weight=groupName,8
- id: weight_low #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
uri: https://juejin.cn/user/2700056290405815
predicates: # 斷言
- Weight=groupName,2
直接存取http://localhost:9006/
可以看到我們請求的地址成8/2比例交替顯示, 80% 的流量轉發到https://blog.csdn.net/qq_14996421,將約 20% 的流量轉發到https://juejin.cn/user/2700056290405815
Predicate就是為了實現一組匹配規則,讓請求過來找到對應的Route進行處理。如果有多個斷言則全部命中後進行處理
路由過濾器允許修改傳入的HTTP請求或者返回的HTTP響應,路由過濾器的範圍是特定的路由.
Spring Cloud GateWay 內建的Filter生命週期有兩種:pre(業務邏輯之前)、post(業務邏輯之後)
GateWay本身自帶的Filter分為兩種: GateWayFilter(單一)、GlobalFilter(全域性)
GateWay Filter提供了豐富的過濾器的使用,單一的有32種,全域性的有9種,有興趣的小夥伴可以瞭解一下。
官方參考網址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
StripPrefix 在我們當前請求中,通過規則值去掉某一部分地址,比如我們有一臺服務中加入了一個前端nacos-provider
想要通過這個去存取,我們在專案cloud-alibaba-nacos-9001
中加入 context-path
server:
port: 9001
servlet:
context-path: /nacos-provider
現在9001的存取路徑變為http://localhost:9001/nacos-provider/mxn/hello
,但是如果我們通過閘道器去存取路徑就會變成http://localhost:9006/mxn/nacos-provider/mxn/hello
這個時候我們通過這個路徑去存取是存取不成功的,想要解決這個方法,這個就用到了我們FIlter
中的 StripPrefix
routes: # 路由
- id: nacos-provider #路由ID,沒有固定要求,但是要保證唯一,建議配合服務名
uri: lb://nacos-provider
predicates: # 斷言
- Path=/mxn/** # 匹配對應地址
filters:
- StripPrefix=1 # 去掉地址中的第一部分
我們重新啟動9006專案,再去存取
雖然Gateway給我們提供了豐富的內建Filter,但是實際專案中,自定義Filter的場景非常常見,因此單獨介紹下自定義FIlter的使用。
想要實現GateWay自定義過濾器,那麼我們需要實現GatewayFilter介面和Ordered介面
@Slf4j
@Component
public class MyFilter implements Ordered, GlobalFilter {
/**
* @param exchange 可以拿到對應的request和response
* @param chain 過濾器鏈
* @return 是否放行
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//獲取第一個引數
String id = exchange.getRequest().getQueryParams().getFirst("id");
//列印當前時間
log.info("MyFilter 當前請求時間為:"+new Date());
//判斷使用者是否存在
if(StringUtils.isEmpty(id)){
log.info("使用者名稱不存在,非法請求!");
//如果username為空,返回狀態碼為407,需要代理身份驗證
exchange.getResponse().setStatusCode(HttpStatus.PROXY_AUTHENTICATION_REQUIRED);
// 後置過濾器
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 設定過濾器的優先順序,值越小則優先順序越高
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
當我們存取http://localhost:9006/mxn/nacos-provider/mxn/hello
請求,沒有攜帶ID引數,請求失敗
當我們存取http://localhost:9006/mxn/nacos-provider/mxn/hello?id=1
請求,請求成功
到這裡我們的GateWay
就講解完了,對於GateWay的核心點主要有三個Route\Predicate\Filter
,我們搞懂了這三點,基本上對於GateWay
的知識就掌握的差不多了,GateWay核心的流程就是:路由轉發+執行過濾器鏈,如果對文中有疑問的小夥伴,歡迎留言討論。
創作不易,如果文中對你有幫助,記得點贊關注,您的支援是我創作的最大動力。
我是牧小農,怕什麼真理無窮,進一步有進一步的歡喜~