Spring-cloud动态路由“动态”的理解,30s刷新一次路由的原理解析

非动态

众所周知,gateway配置最后会被封装成RouteDefinition

可以通过硬编码来配置路由

读取yml文件配置路由

@Configuration
public class DynamicRouteAutoConfiguration {
	/**
	 * 配置文件设置为空
	 * redis 加载为准
	 *
	 * @return
	 */
	@Bean
	public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() {
		return new PropertiesRouteDefinitionLocator(new GatewayProperties());
	}
}

但是这两种方式并不能动态的增删改路由信息,必须要改配置文件还要重启服务。。。

动态

即,数据库+缓存

动态路由接口RouteDefinitionRepository

RouteDefinitionRepository为路由定义定位器,动态路由入口都是基于此接口,内部是只有一个getRouteDefinitions方法
在这里插入图片描述

实现RouteDefinitionRepository接口自定义路由配置规则

RouteDefinitionRepository 通过继承自 RouteDefinitionLocator、 RouteDefinitionWriter,封装了对路由定义信息的获取、增加、删除操作,在网关内置API端点接口时会用到这些操作。

采用数据库+redis配置路由信息

自定义RedisRouteDefinitionWriter路由操作类

这里省略将数据库路由配置信息加载到redis的代码片段,直接看将redis中的路由信息加载到gateway服务中

@Slf4j
@Component
@AllArgsConstructor
public class RedisRouteDefinitionWriter implements RouteDefinitionRepository {
	private final RedisTemplate redisTemplate;

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			RouteDefinitionVo vo = new RouteDefinitionVo();
			BeanUtils.copyProperties(r, vo);
			log.info("保存路由信息{}", vo);
			redisTemplate.setKeySerializer(new StringRedisSerializer());
			redisTemplate.opsForHash().put(CommonConstants.ROUTE_KEY, r.getId(), vo);
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		routeId.subscribe(id -> {
			log.info("删除路由信息{}", id);
			redisTemplate.setKeySerializer(new StringRedisSerializer());
			redisTemplate.opsForHash().delete(CommonConstants.ROUTE_KEY, id);
		});
		return Mono.empty();
	}

	/**
	 * 动态路由入口
	 *
	 * @return
	 */
	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
		List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CommonConstants.ROUTE_KEY);
		List<RouteDefinition> definitionList = new ArrayList<>();
		values.forEach(vo -> {
			RouteDefinition routeDefinition = new RouteDefinition();
			BeanUtils.copyProperties(vo, routeDefinition);
			definitionList.add(vo);
		});
		log.debug("redis 中路由定义条数: {}, {}", definitionList.size(), definitionList);
		return Flux.fromIterable(definitionList);
	}
}

getRouteDefinitions就是获取路由配置的方法,每隔一段时间系统会自动调用
在这里插入图片描述
所以,通过接口往数据库中操作路由配置信息时(同时更新到redis),gateway服务中的路由信息也会得到更新,这应该就是动态路由了。

动态路由引申出的事件监听器

当我看到每隔一段时间打印出路由信息,而且是稳定的30s,这不禁引起我的疑问。getRouteDefinitions方法是在哪里触发的

开启debugger调用链反追踪

在这里插入图片描述

RefreshRoutesEvent事件

忽略不重要的过程,看到一段源码WeightCalculatorWebFilter.java

监听


这里可以看出是通过监听RefreshRoutesEvent事件来触发获取路由信息的,继续往下跟,找到RouteRefreshListener.java

发布

在这里插入图片描述
是在这里发布的RefreshRoutesEvent事件

@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ContextRefreshedEvent
				|| event instanceof RefreshScopeRefreshedEvent
				|| event instanceof InstanceRegisteredEvent) {
			reset();
		}
		else if (event instanceof ParentHeartbeatEvent) {
			ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
			resetIfNeeded(e.getValue());
		}
		else if (event instanceof 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));
	}

可以看到,这里又有一个onApplicationEvent事件监听器,通过判断事件来执行不同方法。

HeartbeatEvent事件

这里关注HeartbeatEvent事件:心跳事件

监听

在这里插入图片描述
继续往下跟,找到CloudEurekaClient.java

发布

在这里发布的HeartbeatEvent事件

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));
        }

    }

动态路由引申出的相关线程

线程CacheRefreshThread

顾名思义,应该是缓存刷新的时候执行onCacheRefreshed,继续往下跟,找到DiscoveryClient.class的内部类,它是一个线程
在这里插入图片描述

线程TimedSupervisorTask

这个线程是什么时候执行的,继续往下跟,找到TimedSupervisorTask.class,它自己是一个时间周期线程,内部维护了一个线程池去执行CacheRefreshThread
在这里插入图片描述
那么TimedSupervisorTask在哪里被调用,找到DiscoveryClient.class
在这里插入图片描述
这里的心跳时间配置就是

  • eureka.client.registry-fetch-interval-seconds:指示从eureka服务器获取注册表信息的频率(默认30s)

后面就是怎么加载配置信息了。。。

大概链路:
项目启动——>运行TimedSupervisorTask——>内部线程池执行CacheRefreshThread——>触发HeartbeatEvent事件——>RefreshRoutesEvent事件——>执行getRouteDefinitions

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值