spring cloud gateway 启用服务发现路由,实例上线后不能立即可用

1,215 阅读3分钟

gateway 服务发现路由

前言

设置 spring.cloud.gateway.discovery.locator.enabled=true, 这样就可用启动自动发现服务路由,不用单独配路由

spring:
  cloud:
    nacos:
      discovery:
        username: nacos
        password: nacos
        server-addr: 127.0.0.1:8848
        namespace: dev
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
  • 默认实现 GatewayDiscoveryClientAutoConfiguration.java
public static List<PredicateDefinition> initPredicates() {
    ArrayList<PredicateDefinition> definitions = new ArrayList();
    PredicateDefinition predicate = new PredicateDefinition();
    predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));
    // 默认匹配服务id为前缀的路由
    predicate.addArg("pattern", "'/'+serviceId+'/**'");
    definitions.add(predicate);
    return definitions;
}

public static List<FilterDefinition> initFilters() {
    ArrayList<FilterDefinition> definitions = new ArrayList();
    FilterDefinition filter = new FilterDefinition();
    filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
    // 默认匹配后会去掉服务id部分路径
    String regex = "'/' + serviceId + '/?(?<remaining>.*)'";
    String replacement = "'/${remaining}'";
    filter.addArg("regexp", regex);
    filter.addArg("replacement", replacement);
    definitions.add(filter);
    return definitions;
}

1、默认匹配服务id为前缀的路由
2、默认匹配后会去掉服务id部分路径
3、示例: /sys/version 会匹配sys服务,匹配后的转发路径就是/version

请求流程

1 首先 DispatcherHandler.java 的handle方法执行

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }
   if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
      return handlePreFlight(exchange);
   }
   return Flux.fromIterable(this.handlerMappings)
           // getHandler方法执行
         .concatMap(mapping -> mapping.getHandler(exchange))
         .next()
         .switchIfEmpty(createNotFoundError())
         .flatMap(handler -> invokeHandler(exchange, handler))
         .flatMap(result -> handleResult(exchange, result));
}

2 AbstractHandlerMapping mapping.getHandler(exchange)这个方法执行

@Override
public Mono<Object> getHandler(ServerWebExchange exchange) {
    // 执行实现类getHandlerInternal
   return getHandlerInternal(exchange).map(handler -> {
      if (logger.isDebugEnabled()) {
         logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
      }
      ServerHttpRequest request = exchange.getRequest();
      if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
         CorsConfiguration config = (this.corsConfigurationSource != null ?
               this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
         CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
         config = (config != null ? config.combine(handlerConfig) : handlerConfig);
         if (config != null) {
            config.validateAllowCredentials();
         }
         if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
            return NO_OP_HANDLER;
         }
      }
      return handler;
   });
}

3 RoutePredicateHandlerMapping getHandlerInternal(exchange)执行


// 路由定义
private final RouteLocator routeLocator;

protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
    if (this.managementPortType == RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT && this.managementPort != null && exchange.getRequest().getURI().getPort() == this.managementPort) {
        return Mono.empty();
    } else {
        exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());
        // 获取路由
        return this.lookupRoute(exchange).flatMap((r) -> {
            exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
            }

            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
            return Mono.just(this.webHandler);
        }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
            exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
            }

        })));
    }
}

// 通过这个方法获取路由
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
        // 获取路由routeLocator.getRoutes()
        return this.routeLocator.getRoutes().concatMap((route) -> {
            return Mono.just(route).filterWhen((r) -> {
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
                return (Publisher)r.getPredicate().apply(exchange);
            }).doOnError((e) -> {
                this.logger.error("Error applying predicate for route: " + route.getId(), e);
            }).onErrorResume((e) -> {
                return Mono.empty();
            });
        }).next().map((route) -> {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Route matched: " + route.getId());
            }

            this.validateRoute(route, exchange);
            return route;
        });
    }

4 CachingRouteLocator routeLocator.getRoutes()执行

public class CachingRouteLocator implements Ordered, RouteLocator, ApplicationListener<RefreshRoutesEvent>, ApplicationEventPublisherAware {
    private static final Log log = LogFactory.getLog(CachingRouteLocator.class);
    private static final String CACHE_KEY = "routes";
    private final RouteLocator delegate;
    private final Flux<Route> routes;
    private final Map<String, List> cache = new ConcurrentHashMap();
    private ApplicationEventPublisher applicationEventPublisher;

    public CachingRouteLocator(RouteLocator delegate) {
        this.delegate = delegate;
        // 这里会进行缓存处理,没有缓存时通过fetch执行代理获取
        this.routes = CacheFlux.lookup(this.cache, "routes", Route.class).onCacheMissResume(this::fetch);
    }

    private Flux<Route> fetch() {
        return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
    }

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

    public Flux<Route> refresh() {
        this.cache.clear();
        return this.routes;
    }

    // 关键就是RefreshRoutesEvent事件会刷新路由缓存
    public void onApplicationEvent(RefreshRoutesEvent event) {
        try {
            this.fetch().collect(Collectors.toList()).subscribe((list) -> {
                Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe((signals) -> {
                    this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));
                    this.cache.put("routes", signals);
                }, this::handleRefreshError);
            }, this::handleRefreshError);
        } catch (Throwable var3) {
            this.handleRefreshError(var3);
        }

    }

    private void handleRefreshError(Throwable throwable) {
        if (log.isErrorEnabled()) {
            log.error("Refresh routes error !!!", throwable);
        }

        this.applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this, throwable));
    }

    public int getOrder() {
        return 0;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
}

默认启用了路由缓存,通过监听RefreshRoutesEvent事件使缓存刷新

5 RefreshRoutesEvent事件是通过这个监听器RouteRefreshListener.reset()发出的

public class RouteRefreshListener implements ApplicationListener<ApplicationEvent> {
    private final ApplicationEventPublisher publisher;
    private HeartbeatMonitor monitor = new HeartbeatMonitor();

    public RouteRefreshListener(ApplicationEventPublisher publisher) {
        Assert.notNull(publisher, "publisher may not be null");
        this.publisher = publisher;
    }

    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ContextRefreshedEvent) {
            ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent)event;
            if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {
                this.reset();
            }
        } else if (!(event instanceof RefreshScopeRefreshedEvent) && !(event instanceof InstanceRegisteredEvent)) {
            if (event instanceof ParentHeartbeatEvent) {
                ParentHeartbeatEvent e = (ParentHeartbeatEvent)event;
                this.resetIfNeeded(e.getValue());
            } else if (event instanceof HeartbeatEvent) {
                HeartbeatEvent e = (HeartbeatEvent)event;
                // 1 这里可能执行发布
                this.resetIfNeeded(e.getValue());
            }
        } else {
            // 2 这里执行发布
            this.reset();
        }

    }

    private void resetIfNeeded(Object value) {
        if (this.monitor.update(value)) {
            this.reset();
        }

    }

    // 这个地方发出
    private void reset() {
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

可用看到有两种地方会执行reset方法,
1、HeartbeatEvent事件 2、RefreshScopeRefreshedEvent、InstanceRegisteredEvent

6 集成nacos后通过NacosWatch发送HeartbeatEvent事件

public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean {

   private static final Logger log = LoggerFactory.getLogger(NacosWatch.class);

   private Map<String, EventListener> listenerMap = new ConcurrentHashMap<>(16);

   private final AtomicBoolean running = new AtomicBoolean(false);

   private final AtomicLong nacosWatchIndex = new AtomicLong(0);

   private ApplicationEventPublisher publisher;

   private ScheduledFuture<?> watchFuture;

   private NacosServiceManager nacosServiceManager;

   private final NacosDiscoveryProperties properties;

   private final ThreadPoolTaskScheduler taskScheduler;

   public NacosWatch(NacosServiceManager nacosServiceManager,
         NacosDiscoveryProperties properties) {
      this.nacosServiceManager = nacosServiceManager;
      this.properties = properties;
      this.taskScheduler = getTaskScheduler();
   }

   @Deprecated
   public NacosWatch(NacosServiceManager nacosServiceManager,
         NacosDiscoveryProperties properties,
         ObjectProvider<ThreadPoolTaskScheduler> taskScheduler) {
      this.nacosServiceManager = nacosServiceManager;
      this.properties = properties;
      this.taskScheduler = taskScheduler.stream().findAny()
            .orElseGet(NacosWatch::getTaskScheduler);
   }

   private static ThreadPoolTaskScheduler getTaskScheduler() {
      ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
      taskScheduler.setBeanName("Nacos-Watch-Task-Scheduler");
      taskScheduler.initialize();
      return taskScheduler;
   }

   @Override
   public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
      this.publisher = publisher;
   }

   @Override
   public void start() {
      if (this.running.compareAndSet(false, true)) {
         EventListener eventListener = listenerMap.computeIfAbsent(buildKey(),
               event -> new EventListener() {
                  @Override
                  public void onEvent(Event event) {
                     if (event instanceof NamingEvent) {
                        List<Instance> instances = ((NamingEvent) event)
                              .getInstances();
                        Optional<Instance> instanceOptional = selectCurrentInstance(
                              instances);
                        instanceOptional.ifPresent(currentInstance -> {
                           resetIfNeeded(currentInstance);
                        });
                     }
                  }
               });

         NamingService namingService = nacosServiceManager
               .getNamingService(properties.getNacosProperties());
         try {
            namingService.subscribe(properties.getService(), properties.getGroup(),
                  Arrays.asList(properties.getClusterName()), eventListener);
         }
         catch (Exception e) {
            log.error("namingService subscribe failed, properties:{}", properties, e);
         }
         // 1 这里启用定时任务,频率properties.getWatchDelay()
         this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
               this::nacosServicesWatch, this.properties.getWatchDelay());
      }
   }

   public void nacosServicesWatch() {
      // 2 这里启用定时任务,执行的方法,默认30s
      // nacos doesn't support watch now , publish an event every 30 seconds.
      this.publisher.publishEvent(
            new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement()));

   }

}

默认watch事件间隔,spring.cloud.nacos.discovery.watchDelay=30000ms,所以新实例上线后会有0-30s的随机时延

处理

监听InstancesChangeEvent事件,发送RefreshRoutesEvent事件

@Slf4j
@RequiredArgsConstructor
@Component
public class GatewayStartListener implements ApplicationListener<ApplicationReadyEvent> {

    private final ApplicationEventPublisher publisher;


    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        log.info("started:{}", event.getTimeTaken());
        NotifyCenter.registerSubscriber(new Subscriber<InstancesChangeEvent>() {
            @Override
            public void onEvent(InstancesChangeEvent event) {
                log.info("onEvent:{}", event);
                publisher.publishEvent(new RefreshRoutesEvent(event));
            }

            @Override
            public Class<? extends Event> subscribeType() {
                return InstancesChangeEvent.class;
            }
        });
        log.info("started registerSubscriber InstancesChangeEvent");
    }
}