大家好,我是冰點(diǎn),今天和大家分享一下關(guān)于Spring Cloud Gateway 利用服務(wù)注冊與發(fā)現(xiàn)實(shí)現(xiàn)自動(dòng)路由的原理和源碼解讀。希望對大家有所幫助。
一、前言
今天有個(gè)新同學(xué),問我
為什么我們的網(wǎng)關(guān)服務(wù)Spring Cloud Gateway,沒有配置路由就可以將請求到路由服務(wù)
,說他們之前的項(xiàng)目的網(wǎng)關(guān)是將路由配置在配置文件中。指定路由類似以下寫法。而在現(xiàn)在的項(xiàng)目的配置文件中未發(fā)現(xiàn)任何路由配置。覺得很奇怪,Spring-Cloud-Gateway 是如何將請求路由到指定的服務(wù)的呢。我讓他比對一下配置文件有什么不同,他說就是只有一個(gè)spring.cloud.gateway.discovery.locator.enabled=true
如下配置一般是大多數(shù)項(xiàng)目配置路由的我們一般稱之為靜態(tài)路由,是由配置文件硬編碼后在程序啟動(dòng)的時(shí)候加載的。
spring:
cloud:
gateway:
discovery:
locator:
lower-case-service-id: true # 忽略服務(wù)名的大小寫
routes:
- id: service1
uri: lb://service1
predicates:
- Path=/service1/**
- id: service2
uri: lb://service2
predicates:
- Path=/service2/**
除了上述的路由配置外,其實(shí)我們通俗的將gateway 的路由可以分為三種
- 靜態(tài)路由
- 動(dòng)態(tài)路由
- 自動(dòng)路由
下面我們詳細(xì)了解一下這三種路由
Spring Cloud Gateway 支持三種類型的路由:靜態(tài)路由、動(dòng)態(tài)路由和自動(dòng)路由。
二、路由配置
1. 靜態(tài)路由
靜態(tài)路由是指在配置文件中預(yù)先定義好的路由規(guī)則,它們在應(yīng)用啟動(dòng)時(shí)就已經(jīng)存在。靜態(tài)路由的優(yōu)點(diǎn)是可以快速定位和處理請求,缺點(diǎn)是需要手動(dòng)配置,不支持動(dòng)態(tài)添加、修改和刪除路由規(guī)則。
在 Spring Cloud Gateway 中,可以通過配置文件來定義靜態(tài)路由規(guī)則。例如:
spring:
cloud:
gateway:
routes:
- id: service1
uri: http://localhost:8081
predicates:
- Path=/service1/**
- id: service2
uri: http://localhost:8082
predicates:
- Path=/service2/**
這段配置文件定義了兩個(gè)靜態(tài)路由規(guī)則,分別對應(yīng)于服務(wù) service1 和服務(wù) service2。當(dāng)請求的路徑匹配 /service1/** 時(shí),它就會(huì)被轉(zhuǎn)發(fā)到 http://localhost:8081;當(dāng)請求的路徑匹配 /service2/** 時(shí),它就會(huì)被轉(zhuǎn)發(fā)到 http://localhost:8082。
2. 動(dòng)態(tài)路由
動(dòng)態(tài)路由是指在運(yùn)行時(shí)動(dòng)態(tài)添加、修改和刪除路由規(guī)則,可以根據(jù)不同的條件動(dòng)態(tài)地調(diào)整路由規(guī)則,例如根據(jù)請求路徑、請求頭、請求參數(shù)等條件。動(dòng)態(tài)路由的優(yōu)點(diǎn)是可以根據(jù)實(shí)際情況調(diào)整路由規(guī)則,缺點(diǎn)是需要額外的管理和維護(hù)成本。
在 Spring Cloud Gateway 中,可以通過 API 來動(dòng)態(tài)添加、修改和刪除路由規(guī)則。例如:
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
public void addRoute(String id, String uri, String predicates) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(id);
routeDefinition.setUri(URI.create(uri));
routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
}
public void updateRoute(String id, String uri, String predicates) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(id);
routeDefinition.setUri(URI.create(uri));
routeDefinition.setPredicates(Collections.singletonList(new PredicateDefinition(predicates)));
routeDefinitionWriter.delete(Mono.just(routeDefinition.getId())).then(Mono.defer(() -> {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
return Mono.empty();
})).subscribe();
}
public void deleteRoute(String id) {
routeDefinitionWriter.delete(Mono.just(id)).subscribe();
}
通過注入
RouteDefinitionWriter
對象來操作路由規(guī)則。addRoute
方法可以添加一條路由規(guī)則,updateRoute
方法可以修改一條路由規(guī)則,deleteRoute
方法可以刪除一條路由規(guī)則。這些操作會(huì)實(shí)時(shí)生效,不需要重啟應(yīng)用。需要在 Spring Boot 應(yīng)用啟動(dòng)時(shí)加載RouteDefinitionLocator
對象,以便正確加載動(dòng)態(tài)路由規(guī)則。
3. 自動(dòng)路由
自動(dòng)路由是指根據(jù)服務(wù)注冊中心的服務(wù)信息自動(dòng)生成路由規(guī)則。當(dāng)有新的服務(wù)上線或下線時(shí),路由規(guī)則也會(huì)自動(dòng)更新。自動(dòng)路由的優(yōu)點(diǎn)是可以根據(jù)實(shí)際情況自動(dòng)調(diào)整路由規(guī)則,缺點(diǎn)是需要服務(wù)注冊中心的支持。其實(shí)服務(wù)發(fā)現(xiàn)可以支持很多種,主要實(shí)現(xiàn)spring cloud 提供的接口即可。下次我專門寫一篇介紹
服務(wù)發(fā)現(xiàn)功能的實(shí)現(xiàn)可以通過 Spring Cloud Commons 中的 DiscoveryClient 類實(shí)現(xiàn)。Spring Cloud
Discovery 可以與多種服務(wù)發(fā)現(xiàn)組件集成,包括Eureka
、Consul
、Zookeeper
等。Spring Cloud
Gateway 會(huì)自動(dòng)與 Spring Cloud Discovery 集成,可以使用 Spring Cloud Discovery
來獲取服務(wù)實(shí)例列表,并將這些服務(wù)實(shí)例轉(zhuǎn)換為路由規(guī)則。大家感興趣可以先了解一下這兩個(gè)接口。
在 Spring Cloud Gateway 中,可以通過配置服務(wù)注冊中心來啟用自動(dòng)路由功能。例如:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
啟用了服務(wù)發(fā)現(xiàn)功能,并將服務(wù) ID 轉(zhuǎn)換為小寫
三、Spring Cloud Gateway 是如何實(shí)現(xiàn)動(dòng)態(tài)路由
客戶端發(fā)送請求到Spring Cloud Gateway,Gateway Handler Mapping確定請求與路由匹配,則會(huì)將請求交給Gateway Web Handler處理。
工作原理
圖片來源spring官網(wǎng) https://cloud.spring.io/spring-cloud-gateway/reference/html/
源碼解析
Spring Cloud Gateway 是一款基于 Spring Framework 和 Spring Boot 的網(wǎng)關(guān)框架,它提供了統(tǒng)一的路由轉(zhuǎn)發(fā)、負(fù)載均衡、請求過濾和請求轉(zhuǎn)換等功能。在 Spring Cloud Gateway 中,路由轉(zhuǎn)發(fā)是其中最核心的功能之一。
下面是 Spring Cloud Gateway 路由轉(zhuǎn)發(fā)的原理和源碼解析。
路由轉(zhuǎn)發(fā)原理
-
Spring Cloud Gateway 的路由轉(zhuǎn)發(fā)基于 Netty 和 Reactor 實(shí)現(xiàn)。當(dāng)一個(gè)請求到達(dá) Spring Cloud
Gateway 時(shí),它會(huì)首先經(jīng)過一系列過濾器的處理,然后根據(jù)路由規(guī)則將請求轉(zhuǎn)發(fā)到正確的目標(biāo)地址。 -
路由規(guī)則由路由配置組件管理,它可以通過多種方式來創(chuàng)建,例如基于配置文件的路由配置、基于 Java代碼的路由配置、基于服務(wù)發(fā)現(xiàn)的路由配置等。每個(gè)路由規(guī)則包含一個(gè)路由條件和一個(gè)目標(biāo) URI,當(dāng)一個(gè)請求滿足路由條件時(shí),它就會(huì)被轉(zhuǎn)發(fā)到目標(biāo)
URI。 -
路由條件由路由規(guī)則的路由條件工廠類創(chuàng)建,例如
PathRoutePredicateFactory、HeaderRoutePredicateFactory、MethodRoutePredicateFactory等。它們可以根據(jù)請求的路徑、請求頭、請求方法等條件來判斷一個(gè)請求是否滿足路由條件。 -
目標(biāo) URI 可以通過多種方式指定,例如硬編碼的 URI、基于服務(wù)發(fā)現(xiàn)的 URI、基于請求頭的 URI 等。在確定了目標(biāo) URI 后,Spring Cloud Gateway 會(huì)將請求轉(zhuǎn)發(fā)到目標(biāo) URI,并將響應(yīng)返回給客戶端。
路由轉(zhuǎn)發(fā)源碼解析
在 Spring Cloud Gateway 中,路由轉(zhuǎn)發(fā)的核心代碼位于 org.springframework.cloud.gateway.handler 包中。其中,RoutePredicateHandlerMapping
類是 Spring Cloud Gateway 的路由轉(zhuǎn)發(fā)入口,它繼承了 AbstractHandlerMapping
類,并實(shí)現(xiàn)了其中的 getHandlerInternal
方法。
RoutePredicateHandlerMapping
的源碼解析 為了方便理解,添加了中文注釋
public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {
private final Map<String, RoutePredicateFactory> predicates; // 路由條件工廠類映射表
private final GatewayFilterHandlerFilter filterHandlerFilter; // 過濾器處理器
private final Map<String, Object> globalFilters; // 全局過濾器映射表
private final RouteDefinitionLocator routeDefinitionLocator; // 路由規(guī)則定位器
public RoutePredicateHandlerMapping(List<RoutePredicateFactory> predicates,
GatewayFilterHandlerFilter filterHandlerFilter,
List<GlobalFilter> globalFilters,
RouteDefinitionLocator routeDefinitionLocator) {
this.predicates = predicates.stream()
.collect(Collectors.toMap(RoutePredicateFactory::name, Function.identity())); // 將路由條件工廠類列表轉(zhuǎn)換為路由條件工廠類映射表
this.filterHandlerFilter = filterHandlerFilter;
this.globalFilters = globalFilters.stream()
.collect(Collectors.toMap(GlobalFilter::name, Function.identity())); // 將全局過濾器列表轉(zhuǎn)換為全局過濾器映射表
this.routeDefinitionLocator = routeDefinitionLocator;
setOrder(-1); // 設(shè)置路由轉(zhuǎn)發(fā)的優(yōu)先級(jí)
}
@Override
protected Object getHandlerInternal(ServerHttpRequest request) throws Exception {
List<RouteDefinition> definitions = this.routeDefinitionLocator.getRouteDefinitions().collectList().block(); // 獲取所有路由規(guī)則
if (definitions == null) { // 如果路由規(guī)則列表為空,則返回 null
return null;
}
for (RouteDefinition routeDefinition : definitions) { // 遍歷所有路由規(guī)則
RoutePredicateFactory predicate = this.predicates.get(routeDefinition.getPredicate().getName()); RoutePredicate routePredicate = predicate.apply(routeDefinition.getPredicate().getArgs()); // 創(chuàng)建路由條件
if (routePredicate.test(request)) { // 判斷請求是否滿足路由條件
Route route = new Route(routeDefinition.getId(), routeDefinition.getUri(), routeDefinition.getFilters()); // 創(chuàng)建路由對象
List<GatewayFilter> gatewayFilters = new ArrayList<>(routeDefinition.getFilters()); // 獲取路由規(guī)則中的過濾器
gatewayFilters.addAll(getGlobalFilters()); // 添加全局過濾器
FilteringWebHandler filteringWebHandler = new FilteringWebHandler(new DefaultWebHandler(), new GatewayFilterChain(gatewayFilters)); // 創(chuàng)建過濾器鏈
return new DefaultWebHandlerAdapter().handle(request, filteringWebHandler); // 返回路由轉(zhuǎn)發(fā)處理器
}
}
return null;
}
private Collection<Object> getGlobalFilters() {
return this.globalFilters.values(); // 返回全局過濾器集合
}
}
在 RoutePredicateHandlerMapping 中,首先通過構(gòu)造方法初始化了路由條件工廠類映射表、過濾器處理器、全局過濾器映射表和路由規(guī)則定位器。然后,實(shí)現(xiàn)了 AbstractHandlerMapping 中的 getHandlerInternal 方法。在 getHandlerInternal 方法中,首先獲取所有路由規(guī)則,并遍歷每個(gè)路由規(guī)則。對于每個(gè)路由規(guī)則,將其路由條件工廠類名稱作為 key,從路由條件工廠類映射表中獲取對應(yīng)的路由條件工廠類,并使用路由條件工廠類創(chuàng)建路由條件。然后,判斷當(dāng)前請求是否滿足路由條件,如果滿足,則創(chuàng)建路由對象,并獲取路由規(guī)則中的過濾器和全局過濾器。將這些過濾器組成過濾器鏈,并將過濾器鏈和默認(rèn)的 Web 處理器一起作為參數(shù)創(chuàng)建過濾器 Web 處理器。最后,使用過濾器 Web 處理器和當(dāng)前請求創(chuàng)建 DefaultWebHandlerAdapter 的實(shí)例,并返回路由轉(zhuǎn)發(fā)處理器。
寫到這兒其實(shí)我們只是了解了一個(gè)請求在路由到后臺(tái)服務(wù)之前必須要要經(jīng)過的幾道工序,就如同我最開始從Spring 官網(wǎng)獲得的工作原理圖。
四 、問題核心
我們來回答最開始的那個(gè)問題。那么如果在不配置路由規(guī)則的Spring Cloud Gateway 服務(wù)中,網(wǎng)關(guān)是如何做的轉(zhuǎn)發(fā)呢,這才是我們核心問題。
那就不得不說一個(gè)重要的核心的接口和實(shí)現(xiàn)類 位于
spring-cloud-gateway-core-2.x.RELEASE
下。RouteDefinitionLocator
。其實(shí)里面就一個(gè)核心方法getRouteDefinitions
。
DiscoveryClientRouteDefinitionLocator源碼解析
DiscoveryClientRouteDefinitionLocator 是RouteDefinitionLocator 實(shí)現(xiàn)類。是 Spring Cloud Gateway 提供的一個(gè)基于服務(wù)發(fā)現(xiàn)的路由規(guī)則定位器,它可以自動(dòng)將服務(wù)實(shí)例列表轉(zhuǎn)換為路由規(guī)則,從而實(shí)現(xiàn)基于服務(wù)發(fā)現(xiàn)的路由配置。
為了方便大家理解,我在源碼上添加了一些注釋
public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
// 服務(wù)發(fā)現(xiàn)客戶端
private final DiscoveryClient discoveryClient;
// 路由規(guī)則轉(zhuǎn)換器
private final RouteDefinitionLocator routeDefinitionLocator;
// 服務(wù)過濾器
private final Predicate<ServiceInstance> predicate;
// 默認(rèn)使用所有服務(wù)實(shí)例
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator) {
this(discoveryClient, routeDefinitionLocator, instance -> true); // 默認(rèn)使用所有服務(wù)實(shí)例
}
// 第二個(gè)構(gòu)造方法可以指定服務(wù)過濾器
public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, RouteDefinitionLocator routeDefinitionLocator, Predicate<ServiceInstance> predicate) {
this.discoveryClient = discoveryClient;
this.routeDefinitionLocator = routeDefinitionLocator;
this.predicate = predicate;
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(getServiceNames()) // 獲取所有服務(wù)名稱
.flatMap(this::getRoutes) // 遍歷每個(gè)服務(wù)名稱,獲取該服務(wù)的路由規(guī)則
.flatMap(routeDefinitionLocator::getRouteDefinitions) // 轉(zhuǎn)換路由規(guī)則
.doOnNext(route -> logger.debug("RouteDefinition matched: " + route)); // 打印日志
}
private List<String> getServiceNames() {
return discoveryClient.getServices(); // 獲取服務(wù)名稱列表
}
private Mono<RouteDefinition> getRoutes(String serviceName) {
return Mono.just(new RouteDefinition()) // 創(chuàng)建一個(gè)新的 RouteDefinition 對象
.flatMap(routeDefinition -> Flux.fromIterable(getInstances(serviceName))) // 獲取該服務(wù)的所有實(shí)例
.filter(predicate) // 過濾服務(wù)實(shí)例
.map(this::getInstanceRoute) // 將服務(wù)實(shí)例轉(zhuǎn)換為路由規(guī)則
.doOnNext(route -> logger.debug("RouteDefinition created: " + route)) // 打印日志
.reduce(new RouteDefinition(), this::mergeRouteDefinitions); // 合并所有路由規(guī)則
}
private List<ServiceInstance> getInstances(String serviceName) {
return discoveryClient.getInstances(serviceName); // 獲取指定服務(wù)的所有實(shí)例
}
private RouteDefinition getInstanceRoute(ServiceInstance instance) {
RouteDefinition route = new RouteDefinition(); // 創(chuàng)建一個(gè)新的 RouteDefinition 對象
route.setId(instance.getServiceId()); // 設(shè)置路由規(guī)則的 ID 為服務(wù)名稱
URI uri = instance.getUri(); // 獲取服務(wù)實(shí)例的 URI
if (uri != null) {
route.setUri(uri); // 設(shè)置路由規(guī)則的 URI
}
return route;
}
private RouteDefinition mergeRouteDefinitions(RouteDefinition route1, RouteDefinition route2) {
route1.getFilters().addAll(route2.getFilters()); // 合并過濾器
route1.getPredicates().addAll(route2.getPredicates()); // 合并謂詞
return route1;
}
}
- DiscoveryClientRouteDefinitionLocator 類的主要作用是從服務(wù)注冊中心獲取服務(wù)信息并將其轉(zhuǎn)換為路由規(guī)則。它實(shí)現(xiàn)了 RouteDefinitionLocator 接口,用于獲取路由規(guī)則列表。具體來說,它通過 DiscoveryClient 類獲取所有服務(wù)名稱,遍歷每個(gè)服務(wù)名稱,再通過 DiscoveryClient 類獲取該服務(wù)的所有實(shí)例,最后將實(shí)例信息轉(zhuǎn)換為路由規(guī)則。
- getRouteDefinitions 方法是 DiscoveryClientRouteDefinitionLocator類的核心方法,用于獲取所有的路由規(guī)則。它通過 Flux.fromIterable() 獲取所有服務(wù)名稱,然后通過 flatMap()方法遍歷每個(gè)服務(wù)名稱,獲取該服務(wù)的路由規(guī)則。獲取路由規(guī)則的方法是 getRoutes(),該方法通過 Mono.just()創(chuàng)建一個(gè)新的 RouteDefinition 對象,然后通過 Flux.fromIterable() 獲取該服務(wù)的所有實(shí)例,再通過filter() 方法過濾服務(wù)實(shí)例,接著調(diào)用getInstanceRoute方法將服務(wù)實(shí)例轉(zhuǎn)換為路由規(guī)則,最后通過reduce() 方法將所有路由規(guī)則合并成一個(gè)RouteDefinition 對象。在合并路由規(guī)則時(shí),會(huì)調(diào) mergeRouteDefinitions 方法實(shí)現(xiàn)合并過濾器和謂詞的操作。
- getServiceNames獲取所有服務(wù)名稱,它通過 DiscoveryClient 類的 getServices() 方法實(shí)現(xiàn)。
- getInstances() 獲取指定服務(wù)的所有實(shí)例,它通過 DiscoveryClient 類的 getInstances() 方法實(shí)現(xiàn)。
- getInstanceRoute() 方法用于將服務(wù)實(shí)例轉(zhuǎn)換為路由規(guī)則,它創(chuàng)建一個(gè)新的 RouteDefinition對象,將服務(wù)名稱作為路由規(guī)則的 ID,將服務(wù)實(shí)例的 URI 作為路由規(guī)則的 URI,并返回該路由規(guī)則對象。
- mergeRouteDefinitions方法用于合并路由規(guī)則,它將兩個(gè)路由規(guī)則對象的過濾器和謂詞合并到一個(gè)路由規(guī)則對象中,并返回該路由規(guī)則對象。
五、總結(jié)
所以總而言之要回答上面的問題,還是必須要有服務(wù)注冊與發(fā)現(xiàn)的基礎(chǔ)知識(shí),才能理解。而實(shí)現(xiàn)這個(gè)特性的關(guān)鍵類=DiscoveryClientRouteDefinitionLocator 類,它通過服務(wù)發(fā)現(xiàn)客戶端從服務(wù)注冊中心獲取服務(wù)信息并將其轉(zhuǎn)換為路由規(guī)則,并實(shí)現(xiàn)了 RouteDefinitionLocator接口,用于獲取路由規(guī)則列表。文章來源:http://www.zghlxwxcb.cn/news/detail-475814.html
好了今天的分享就到這兒,希望三分鐘的閱讀對你有所收獲。我是冰點(diǎn),下次再見。文章來源地址http://www.zghlxwxcb.cn/news/detail-475814.html
到了這里,關(guān)于三分鐘了解Spring Cloud Gateway路由轉(zhuǎn)發(fā)之自動(dòng)路由的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!