众所周知,gateway配置最后会被封装成RouteDefinition
。
略
@Configuration
public class DynamicRouteAutoConfiguration {
/**
* 配置文件设置为空
* redis 加载为准
*
* @return
*/
@Bean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() {
return new PropertiesRouteDefinitionLocator(new GatewayProperties());
}
}
但是这两种方式并不能动态的增删改路由信息,必须要改配置文件还要重启服务。。。
即,数据库+缓存
RouteDefinitionRepository
为路由定义定位器,动态路由入口都是基于此接口,内部是只有一个getRouteDefinitions
方法
RouteDefinitionRepository
通过继承自 RouteDefinitionLocator
、 RouteDefinitionWriter
,封装了对路由定义信息的获取、增加、删除操作,在网关内置API端点接口时会用到这些操作。
这里省略将数据库路由配置信息加载到redis的代码片段,直接看将redis中的路由信息加载到gateway服务中
@Slf4j
@Component
@AllArgsConstructor
public class RedisRouteDefinitionWriter implements RouteDefinitionRepository {
private final RedisTemplate redisTemplate;
@Override
public Mono save(Mono 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 delete(Mono 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 getRouteDefinitions() {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
List values = redisTemplate.opsForHash().values(CommonConstants.ROUTE_KEY);
List 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
方法是在哪里触发的
忽略不重要的过程,看到一段源码WeightCalculatorWebFilter.java
这里可以看出是通过监听RefreshRoutesEvent
事件来触发获取路由信息的,继续往下跟,找到RouteRefreshListener.java
@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
事件:心跳事件
继续往下跟,找到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));
}
}
顾名思义,应该是缓存刷新的时候执行onCacheRefreshed
,继续往下跟,找到DiscoveryClient.class
的内部类,它是一个线程
这个线程是什么时候执行的,继续往下跟,找到TimedSupervisorTask.class
,它自己是一个时间周期线程,内部维护了一个线程池去执行CacheRefreshThread
那么TimedSupervisorTask
在哪里被调用,找到DiscoveryClient.class
这里的心跳时间配置就是
eureka.client.registry-fetch-interval-seconds
:指示从eureka服务器获取注册表信息的频率(默认30s)后面就是怎么加载配置信息了。。。
大概链路:
项目启动——>运行TimedSupervisorTask——>内部线程池执行CacheRefreshThread——>触发HeartbeatEvent事件——>RefreshRoutesEvent事件——>执行getRouteDefinitions