pigx动态路由分析(二)
1、动态路由何时生效?
在pigx动态路由分析1中,我介绍了pigx是何时定义的动态路由,当时只分析到路由配置是如何从数据库读取到redis,这个配置放到redis和网关还没有建立关系,如下最后一行就是从mysql把route定义转到redis。
//common-gateway模块 DynamicRouteInitRunner类
@Async
@Order
@EventListener({ WebServerInitializedEvent.class, DynamicRouteInitEvent.class })
public void initRoute() {
log.info("当前线程:"+Thread.currentThread().getName());
Boolean result = redisTemplate.delete(CacheConstants.ROUTE_KEY);
log.info("初始化网关路由 {} ", result);
routeConfService.list().forEach(route -> {
RouteDefinitionVo vo = new RouteDefinitionVo();
vo.setRouteName(route.getRouteName());
vo.setId(route.getRouteId());
vo.setUri(URI.create(route.getUri()));
vo.setOrder(route.getOrder());
JSONArray filterObj = JSONUtil.parseArray(route.getFilters());
vo.setFilters(filterObj.toList(FilterDefinition.class));
JSONArray predicateObj = JSONUtil.parseArray(route.getPredicates());
vo.setPredicates(predicateObj.toList(PredicateDefinition.class));
log.info("加载路由ID:{},{}", route.getRouteId(), vo);
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
});
log.debug("初始化网关路由结束 ");
}
2 分析过程
- 先从启动器着手
先找到gateway模块,进入启动类(ccs是我司项目名,我们购买的是pigx商业版,我们把项目名包名都改成了ccs,和直接下载pigx不一样),可以看到有个@EnableCcsDynamicRoute,这个注解是自定义注解,跟踪进去
@EnableCcsDynamicRoute
@SpringCloudApplication
public class CcsGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(CcsGatewayApplication.class, args);
}
}
- EnableCcsDynamicRoute分析
可以看到此注解啥都没干,就引入了@Import(DynamicRouteAutoConfiguration.class),接着我们再查看DynamicRouteAutoConfiguration
// common-gateway模块
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(DynamicRouteAutoConfiguration.class)
public @interface EnableCcsDynamicRoute {
}
- DynamicRouteAutoConfiguration
看代码得出一下结论:
- 这是个配置类(@Configuration)
- 扫描了com.sonoscape.ccs.common.gateway包
- @ConditionalOnWebApplication此配置类只在响应式编程(和servlet相对)生效,这个不是注解不是重点,主要是前两个
//common-gateway模块
@Slf4j
@Configuration
@ComponentScan("com.sonoscape.ccs.common.gateway")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class DynamicRouteAutoConfiguration {
/**
* 配置文件设置为空 redis 加载为准
* @return
*/
@Bean
public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator() {
return new PropertiesRouteDefinitionLocator(new GatewayProperties());
}
/**
* redis 监听配置
* @param redisConnectionFactory redis 配置
* @return
*/
@Bean
public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener((message, bytes) -> {
log.warn("接收到重新JVM 重新加载路由事件");
RouteCacheHolder.removeRouteList();
}, new ChannelTopic(CacheConstants.ROUTE_JVM_RELOAD_TOPIC));
return container;
}
@Bean
@ConditionalOnProperty(value = "spring.redis.cluster.enable", havingValue = "true")
public LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(
redisProperties.getCluster().getNodes());
// https://github.com/lettuce-io/lettuce-core/wiki/Redis-Cluster#user-content-refreshing-the-cluster-topology-view
ClusterTopologyRefreshOptions clusterTopologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
.enablePeriodicRefresh().enableAllAdaptiveRefreshTriggers().refreshPeriod(Duration.ofSeconds(5))
.build();
ClusterClientOptions clusterClientOptions = ClusterClientOptions.builder()
.topologyRefreshOptions(clusterTopologyRefreshOptions).build();
// https://github.com/lettuce-io/lettuce-core/wiki/ReadFrom-Settings
LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED).clientOptions(clusterClientOptions).build();
return new LettuceConnectionFactory(redisClusterConfiguration, lettuceClientConfiguration);
}
}
- 分析到这里好像也没发现啥?? 我分析到这就卡克了
别忘了,我们第一篇文章中有关键的一步,设置了路由到redis,具体代码是redisTemplate.opsForHash().put(CacheConstants.ROUTE_KEY, route.getRouteId(), vo);
,CacheConstants.ROUTE_KEY,这个是redis里面的key,既然这里有设置,那么必然有地方需要获取路由,从而使用它,只需要查询CacheConstants.ROUTE_KEY,在哪里被使用了即可,因为获取值肯定还是根据key获取。还是老办法,在CacheConstants.ROUTE_KEY,上Alt+F7,查找哪里用到。果不出其然被我找到了,如下图,我们定位到了调用的位置在common-gateway模块里面的RedisRouteDefinitionWriter类,他用了@Component注解,说明是一个bean,下面接着分析
- RedisRouteDefinitionWriter
我们只看关键方法getRouteDefinitions,定位到这里已经可以很明显的看到了pigx作者的注释,这是动态路由入口!!!!
/**
* 动态路由入口
* <p>
* 1. 先从内存中获取 2. 为空加载Redis中数据 3. 更新内存
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinitionVo> routeList = RouteCacheHolder.getRouteList();
if (CollUtil.isNotEmpty(routeList)) {
log.debug("内存 中路由定义条数: {}, {}", routeList.size(), routeList);
return Flux.fromIterable(routeList);
}
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CacheConstants.ROUTE_KEY);
log.debug("redis 中路由定义条数: {}, {}", values.size(), values);
RouteCacheHolder.setRouteList(values);
return Flux.fromIterable(values);
}
- 我迫不及待的在getRouteDefinitions方法打上断点,重新debug启动网关模块,哈哈,启动时自动就进入了断点,为啥会自动进入呢???下面贴个完整代码,可以看到它实现了RouteDefinitionRepository,这个类名很熟悉的赶脚,对我等靠增删改查吃饭的码畜来首再熟悉不过了。
@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(CacheConstants.ROUTE_KEY, r.getId(), vo);
redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "新增路由信息,网关缓存更新");
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
routeId.subscribe(id -> {
log.info("删除路由信息{}", id);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.opsForHash().delete(CacheConstants.ROUTE_KEY, id);
});
redisTemplate.convertAndSend(CacheConstants.ROUTE_JVM_RELOAD_TOPIC, "删除路由信息,网关缓存更新");
return Mono.empty();
}
/**
* 动态路由入口
* <p>
* 1. 先从内存中获取 2. 为空加载Redis中数据 3. 更新内存
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinitionVo> routeList = RouteCacheHolder.getRouteList();
if (CollUtil.isNotEmpty(routeList)) {
log.debug("内存 中路由定义条数: {}, {}", routeList.size(), routeList);
return Flux.fromIterable(routeList);
}
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinitionVo.class));
List<RouteDefinitionVo> values = redisTemplate.opsForHash().values(CacheConstants.ROUTE_KEY);
log.debug("redis 中路由定义条数: {}, {}", values.size(), values);
RouteCacheHolder.setRouteList(values);
return Flux.fromIterable(values);
}
}