天天看點

Spring-Cloud-Gateway 源碼解析 —— 路由(1.4)之 DiscoveryClientRouteDefinitionLocator 注冊中心1. 概述2. 環境搭建3. DiscoveryClientRouteDefinitionLocator4. 高能

1. 概述

本文主要對 DiscoveryClientRouteDefinitionLocator 的源碼實作。

DiscoveryClientRouteDefinitionLocator 通過調用 

org.springframework.cloud.client.discovery.DiscoveryClient

 擷取注冊在注冊中心的服務清單,生成對應的 RouteDefinition 數組。

2. 環境搭建

在解析源碼之前,我們先以 Eureka 為注冊中心,講解下如何配置 DiscoveryClientRouteDefinitionLocator 。

第一步,以 

spring-cloud-gateway-sample

 項目為基礎,在 

pom.xml

 檔案添加依賴庫。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>2.0.0.M1</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </exclusion>
    </exclusions>
</dependency>
           
  • 注意,需要排除 

    spring-boot-starter-web

     的依賴,避免和 Spring Cloud Gateway 依賴的 

    spring-boot-starter-webflux

     沖突。

第二步,在 

application.yml

 添加 Eureka 相關配置 。

spring:
  application:
      name: juejin-gateway

eureka:
  instance:
    leaseRenewalIntervalInSeconds: 10
    leaseExpirationDurationInSeconds: 30
  client:
    serviceUrl:
      defaultZone: http://eureka.didispace.com/eureka/
           
  • 如果你“懶”的啟動 Eureka ,推薦使用 《程式猿DD - 公益 - EUREKA SERVER》 。感謝 1024 。

第三步,在 

org.springframework.cloud.gateway.sample.GatewaySampleApplication

 類裡,添加 RouteDefinitionLocator Bean 配置

@EnableDiscoveryClient // {@link DiscoveryClientRouteDefinitionLocator}
public class GatewaySampleApplication {
    
    // ... 省略其他代碼

    @Bean
    public RouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient);
    }
}
           

第四步,啟動一個注冊在 Eureka 的應用服務。機智如你,這裡我就不啰嗦落。

第五步,在 DiscoveryClientRouteDefinitionLocator 的 

#getRouteDefinitions()

 方法打斷點,調試啟動 

spring-cloud-gateway-sample

 。小功告成。撒花~

3. DiscoveryClientRouteDefinitionLocator

org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator

 ,通過調用 DiscoveryClient 擷取注冊在注冊中心的服務清單,生成對應的 RouteDefinition 數組。

代碼如下 :

1: public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {
 2: 
 3: 	private final DiscoveryClient discoveryClient;
 4: 	private final String routeIdPrefix;
 5: 
 6: 	public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) {
 7: 		this.discoveryClient = discoveryClient;
 8: 		this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_";
 9: 	}
10: 
11: 	@Override
12: 	public Flux<RouteDefinition> getRouteDefinitions() {
13: 		return Flux.fromIterable(discoveryClient.getServices())
14: 				.map(serviceId -> {
15: 					RouteDefinition routeDefinition = new RouteDefinition();
16: 					// 設定 ID
17: 					routeDefinition.setId(this.routeIdPrefix + serviceId);
18: 					// 設定 URI
19: 					routeDefinition.setUri(URI.create("lb://" + serviceId));
20: 
21: 					// add a predicate that matches the url at /serviceId
22: 					/*PredicateDefinition barePredicate = new PredicateDefinition();
23: 					barePredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class));
24: 					barePredicate.addArg(PATTERN_KEY, "/" + serviceId);
25: 					routeDefinition.getPredicates().add(barePredicate);*/
26: 
27: 					// 添加 Path 比對斷言
28: 					// add a predicate that matches the url at /serviceId/**
29: 					PredicateDefinition subPredicate = new PredicateDefinition();
30: 					subPredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class));
31: 					subPredicate.addArg(PATTERN_KEY, "/" + serviceId + "/**");
32: 					routeDefinition.getPredicates().add(subPredicate);
33: 
34: 					//TODO: support for other default predicates
35: 
36: 					// 添加 Path 重寫過濾器
37: 					// add a filter that removes /serviceId by default
38: 					FilterDefinition filter = new FilterDefinition();
39: 					filter.setName(normalizeFilterName(RewritePathGatewayFilterFactory.class));
40: 					String regex = "/" + serviceId + "/(?<remaining>.*)";
41: 					String replacement = "/${remaining}";
42: 					filter.addArg(REGEXP_KEY, regex);
43: 					filter.addArg(REPLACEMENT_KEY, replacement);
44: 					routeDefinition.getFilters().add(filter);
45: 
46: 					//TODO: support for default filters
47: 
48: 					return routeDefinition;
49: 				});
50: 	}
51: }
           
  • discoveryClient

     屬性,注冊發現用戶端,用于向注冊中心發起請求。
  • routeIdPrefix

     屬性,路由配置編号字首,以 DiscoveryClient 類名 + 

    _

     。
  • 第 13 行 :調用 

    discoveryClient

     擷取注冊在注冊中心的服務清單。
  • 第 14 行 :周遊服務清單,生成對應的 RouteDefinition 數組。
  • 第 16 行 :設定 

    RouteDefinition.id

     。
  • 第 18 行 :設定 

    RouteDefinition.uri

     ,格式為 

    lb://${serviceId}

     。在 LoadBalancerClientFilter 會根據 

    lb://

     字首過濾處理,負載均衡,選擇最終調用的服務位址,在 《Spring-Cloud-Gateway 源碼解析 —— 過濾器 (4.4) 之 LoadBalancerClientFilter 負載均衡》 詳細解析。
  • 第 27 至 32 行 :使用 PathRoutePredicateFactory 建立 Path 比對斷言。
    • 例如服務的 

      serviceId = spring.application.name = juejin-sample

       ,通過網關 

      http://${gateway}/${serviceId}/some_api

       通路服務 

      http://some_api

       。
    • PathRoutePredicateFactory 在 《Spring-Cloud-Gateway 源碼解析 —— 處理器 (3.1) 之 RoutePredicateFactory 路由謂語工廠》「10. PathRoutePredicateFactory」 有詳細解析。
  • 第 36 至 44 行 :使用 RewritePathGatewayFilterFactory 建立重寫網關過濾器,用于移除請求路徑裡的 

    /${serviceId}

     。如果不移除,最終請求不到服務。RewritePathGatewayFilterFactory 在 《Spring-Cloud-Gateway 源碼解析 —— 過濾器 (4.2) 之 GatewayFilterFactory 過濾器工廠》「4.1 RewritePathGatewayFilterFactory」 有詳細解析。
  • 第 48 行 :傳回路由定義 RouteDefinition 。舉個 RouteDefinition 例子 :
    Spring-Cloud-Gateway 源碼解析 —— 路由(1.4)之 DiscoveryClientRouteDefinitionLocator 注冊中心1. 概述2. 環境搭建3. DiscoveryClientRouteDefinitionLocator4. 高能

4. 高能

本小節建議閱讀完 《Spring-Cloud-Gateway 源碼解析 —— 路由(2.1)之 RouteLocator 一覽》 再了解。

RoutePredicateHandlerMapping 使用 CachingRouteLocator 來擷取 Route 資訊。在 Spring Cloud Gateway 啟動後,如果有新加入的服務,則需要重新整理 CachingRouteLocator 緩存。

這裡有一點需要注意下 :新加入的服務,指的是新的 

serviceId

 ,而不是原有服務新增的執行個體。

個人建議,寫一個定時任務,間隔調用 DiscoveryClient 擷取服務清單,若發現變化,重新整理 CachingRouteLocator 緩存。

繼續閱讀