gateway路由加载及刷新

381 阅读4分钟

1.路由加载

1. 路由配置

spring:
  cloud:
    gateway:
      routes:
        - id : rewritepath
          predicates:
            - Path=/jzk-app-eureka-dev/jzkApp/**
          uri: lb://JZK-EUREKA-DEV
          filters:
            - RewritePath=/jzk-app-eureka-dev/jzkApp,/jzkAppdev
        - id: transform
          predicates:
            - Path=/mizar-cloud-auth/**
          uri: lb://MIZAR-CLOUD-AUTH-DEV
          filters:
            - RewritePath=/mizar-cloud-auth,/mizar-cloud-auth-dev
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@remoteAddrKeyResolver}'
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1

结合@ConfigurationProperties 的日常使用 , 我们可以知道 routes是一个对象数组,一个route对象包含id ,predicates,url,filters 属性,其中 predicates ,filters 也是对象数组。

直接ctrl + 左键点击  routes ,可以直接 找到

GatewayProperties  
@ConfigurationProperties("spring.cloud.gateway")
public class GatewayProperties {
    private List<RouteDefinition> routes = new ArrayList();
    private List<FilterDefinition> defaultFilters = new ArrayList();
    private List<MediaType> streamingMediaTypes;
    private boolean failOnRouteDefinitionError;
}

GatewayAutoConfiguration 可以看到 最终的配置变成了List routes

ctrl + 左键 ,可以看到 GatewayAutoConfiguration  持有GatewayProperties 的引用  并且赋值给了PropertiesRouteDefinitionLocator  ,RouteDefinitionLocator 路由定义定位器   是不是还有其他的 定位器呢 ?

继续看该类提供的其他Bean

@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) {
    return new PropertiesRouteDefinitionLocator(properties);
}

//基于内存的路由仓库  默认实现,如果你想, 你可以autowired 这个类, 调用它提供的方法 进行刷新路由, 只要你有合适的触发机会

@Bean
@ConditionalOnMissingBean({RouteDefinitionRepository.class}) 
public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
    return new InMemoryRouteDefinitionRepository();
}

//Composite 混合的,并且带上了 @Primary 注解,我们有理由猜测这个类可能是个组合代理类  :它的作用是  组合多种RouteDefinitionLocator ,提供统一入口

@Bean
@Primary
public RouteDefinitionLocator routeDefinitionLocator(List<RouteDefinitionLocator> routeDefinitionLocators) {
    return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators));
}

  //将RouteDefinition转化为 完整的路由对象Route

 @Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> predicates, RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) {
    return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, configurationService);
}

 

//又一个 @Primary  这个类是最终 真正Route对象集合访问的真正入口,因为他具备缓存性,如果没有这个类,在响应式系统中 可能会造成 一个请求就重新 获取所有definition-> 转换路由 -> 匹配理由。

 @Bean
@Primary
@ConditionalOnMissingBean(
    name = {"cachedCompositeRouteLocator"}
)
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
    return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
}

//见名知意  它应该和刷新路由相关 ,并且是事件监听触发

    @Bean
public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {
    return new RouteRefreshListener(publisher);
}

RouteLocator

gateway 路由管理 由三层 RouteLocator组成,

分别为 RouteDefinitionRouteLocator  将RouteDefinition转化为 Route 对象,

            ,CompositeRouteLocator  将路由组合起来,提供统一入口

            ,CachingRouteLocator  是最终进行路由匹配实际操作的对象, 会监听 RefreshRoutesEvent 事件,重新获取 RouteDefinition集合 来实例化路由,并更新缓存

基于微服务名进行转发

DiscoveryClientRouteDefinitionLocator

上文提到CompositeRouteLocator  会将多个 RouteDefinitionRouteLocator  实例组合起来,提供统一入口,DiscoveryClientRouteDefinitionLocator就是专门根据服务名提供路由匹配规则的

spring.cloud.gateway.discovery.locator.enabled:true  开启服务自动发现,应该说根据服务名自动注册路由

    public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {
    @Bean
    @ConditionalOnProperty(
        name = {"spring.cloud.gateway.discovery.locator.enabled"}
    )
    public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
    }
}

DiscoveryClientRouteDefinitionLocator

    public Flux<RouteDefinition> getRouteDefinitions() {

     ...

    //这里的实例列表是内部封装了 com.netflix.discovery.DiscoveryClient实例, 服务在启动时根据是否拉取服务列表配置,默认为true从注册中心拉取服务列表

    return this.serviceInstances.filter((instances) -> {
        return !instances.isEmpty();
    }).map((instances) -> {
        return (ServiceInstance)instances.get(0);
    }).filter(includePredicate).map((instance) -> {
        //从instance中解析 predicate uri信息
        RouteDefinition routeDefinition = this.buildRouteDefinition(urlExpr, instance);

        ServiceInstance instanceForEval = new DiscoveryClientRouteDefinitionLocator.DelegatingServiceInstance(instance, this.properties);
        ...
        return routeDefinition;
    });
}

那么何时进行路由的刷新呢 ?

RouteRefreshListener

public void onApplicationEvent(ApplicationEvent event) {
    if (!(event instanceof ContextRefreshedEvent) && !(event instanceof RefreshScopeRefreshedEvent) && !(event instanceof InstanceRegisteredEvent)) {
        if (event instanceof ParentHeartbeatEvent) {
            ParentHeartbeatEvent e = (ParentHeartbeatEvent)event;
            this.resetIfNeeded(e.getValue());
        } else if (event instanceof HeartbeatEvent) {
            //如果监听到HeartbeatEvent事件 会进行判断是否 再发布RefreshRoutesEvent事件,此事件将触发路由的更新
            HeartbeatEvent e = (HeartbeatEvent)event;
            this.resetIfNeeded(e.getValue());  //调用下面reset
        }
    } else {
        this.reset();  //转身又发了个事件 RefreshRoutesEvent
    }
}

HeartbeatEvent什么时候触发? ** CloudEurekaClient#onCacheRefreshed**

//com.netflix.discovery.DiscoveryClient 定时任务拉取到新的服务列表 进行缓存更新时,会调用onCacheRefreshed方法,此时进行HeartbeatEvent事故发布,从而被RouteRefreshListener监听到,最终触发路由的更新
protected void onCacheRefreshed() {
    super.onCacheRefreshed();
    if (this.cacheRefreshedCount != null) {
        long newCount = this.cacheRefreshedCount.incrementAndGet();
        log.trace("onCacheRefreshed called with count: " + newCount);
        this.publisher.publishEvent(new HeartbeatEvent(this, newCount));
    }
}

RefreshRoutesEvent 被谁接收了?

CachingRouteLocator
    public class CachingRouteLocator implements Ordered, RouteLocator,
    ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {

   private final RouteLocator delegate;
   private final Flux<Route> routes;


   private Flux<Route> fetch() {
       //持有的delegate  是CompositeRouteLocator  它会负责收集包括各种 RouteLocator的实现类 getRoutes方法返回的 Flux<Route>
      return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
   }

   @Override
   public Flux<Route> getRoutes() {
      return this.routes;
   }


   @Override
   public void onApplicationEvent(RefreshRoutesEvent event) {
      try {
         fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list)
               .materialize().collect(Collectors.toList()).subscribe(signals -> {
                  applicationEventPublisher
                        .publishEvent(new RefreshRoutesResultEvent(this));
                  cache.put(CACHE_KEY, signals);
               }, throwable -> handleRefreshError(throwable)));

      }
      catch (Throwable e) {
         handleRefreshError(e);
      }
   }    

2.基于zookeeper动态路由

1.基于Zookeeper的节点监听机制,每个节点的值都是序列化后的RouteDefinition对象,读取指定路径下的所有子节点,获取所有的路由信息,反序列化获得RouteDefinition对象数组

2.监听所有的子节点的值,进行相应的 RouteDefinition对象 更新/删除

3.监听所有指定路径的子节点列表,找到变化后的子节点列表中不在旧节点列表存在的节点(即新增的路由节点),根据节点获取值,转化成RouteDefinition对象,同时监控此节点值的变化。

4.在初始化 / 触发监听时事件后 ,发布RefreshRoutesEvent事件,让gateway 根据List   创建新的 List 对象,进行路由的替换

核心是 监听RefreshRoutesEvent 事件,从新拉取路由定义进行实例化,路由定义可使用db,nosql,apollo等多种实现,需要刷新的时候发布下事件即可