spring cloud gateway 路由刷新

Last Modified: 2023/02/10

概述

Spring cloud 配合 nacos 支持定时刷新路由信息,当新增一个微服务时,无需重启网关,就可以将实现请求转发到该服务。

实现

自动更新路由主要是通过 NacosWatch 实现的,NacosWatch 则是通过自动配置完成的:

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureBefore({ SimpleDiscoveryClientAutoConfiguration.class,
		CommonsClientAutoConfiguration.class })
@AutoConfigureAfter(NacosDiscoveryAutoConfiguration.class)
public class NacosDiscoveryClientConfiguration {

  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnProperty(value = "spring.cloud.nacos.discovery.watch.enabled", matchIfMissing = true)
  public NacosWatch nacosWatch(NacosServiceManager nacosServiceManager,
  		NacosDiscoveryProperties nacosDiscoveryProperties) {
  	return new NacosWatch(nacosServiceManager, nacosDiscoveryProperties);
  }
}

由于 NacosWatch bean 实现了 Lifecycle 接口,容器会调用它的生命周期方法 start(),该方法内部启动了定时任务:

@Override
public void start() {
  if (this.running.compareAndSet(false, true)) {
    // ...
    // 启动定时任务
    this.watchFuture = this.taskScheduler.scheduleWithFixedDelay(
      this::nacosServicesWatch, this.properties.getWatchDelay());
  }
}

该定时任务,默认每隔 30s 发送一个 HeartbeatEvent:

public void nacosServicesWatch() {
  // nacos doesn't support watch now , publish an event every 30 seconds.
  this.publisher.publishEvent(
        new HeartbeatEvent(this, nacosWatchIndex.getAndIncrement()));
}

这个 HeartbeatEvent 会被 GatewayAutoConfiguration 中的 RouteRefreshListener 接收并处理:

@Bean
@ConditionalOnClass(name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor")
public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {
  return new RouteRefreshListener(publisher);
}

RouteRefreshListener 的 onApplicationEvent 方法处理了 HeartbeatEvent:

@Override
public void onApplicationEvent(ApplicationEvent event) {
  if (event instanceof ContextRefreshedEvent) {
    // ...
  }
  else if (event instanceof RefreshScopeRefreshedEvent || event instanceof InstanceRegisteredEvent) {
    // ...
  }
  else if (event instanceof ParentHeartbeatEvent) {
    // ...
  }
  else if (event instanceof HeartbeatEvent) {
    // 处理了 HeartbeatEvent
    HeartbeatEvent e = (HeartbeatEvent) event;
    resetIfNeeded(e.getValue());
  }
}

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

private void reset() {
  this.publisher.publishEvent(new RefreshRoutesEvent(this));
}

RouteRefreshListener 处理方式是接收到 HeartbeatEvent 之后,发布了一个新的 RefreshRoutesEvent。所以我们需要看是谁处理了 RefreshRoutesEvent。通过追查发现是 CachingRouteLocator 处理了该事件:

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

@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);
        }, this::handleRefreshError), this::handleRefreshError);
  } catch (Throwable e) {
    handleRefreshError(e);
  }
}

刷新路由具体来说就是通过 fetch() 方法实现的,该方法会从 nacos 中获取路由信息并更新。因此默认情况下,每隔 30s,网关就会自动更新一次路由信息,如果我们需要改变刷新周期,可以在 bootstrap.yml 中配置如下:

spring: 
  cloud:
    nacos:
      discovery:
        # 配置从 nacos 服务拉取新服务的时间间隔
        watchDelay: 40000
有问题吗?点此反馈!

温馨提示:反馈需要登录