前言
spring cloud gateway默认为内存存储策略,通过配置文件加载的方式生成路由定义信息
可以看到,RouteDefinitionRepository继承了两个父接口,分别为RouteDefinitionLocator和RouteDefinitionWriter,RouteDefinitionLocator定义了路由定义获取接口,而RouteDefinitionWriter则定义了路由定义保存更新save接口和删除接口。
可以看到RouteDefinitionRepository是核心接口,其默认的实现类有上图两个,分别是基于内存和基于redis的,如果需要redis实现动态路由则只需要实现RouteDefinitionRepository接口并注入bean即可
一、实现RouteDefinitionRepository接口
这里其实spring cloud gateway已经有默认实现了的基于redis 的路由定义相关的repository,但由于其并没有做本地缓存,是纯redis的方式读取写入,这样会导致在进行路由调用的时候频繁的调用其getRouteDefinitions方法,频繁的读取redis其实也是有一定的性能损耗,因此我们其实稍加利用,添加本地缓存即可(本示例使用咖啡因缓存),注意添加注解:@EnableCaching
/**
* @classDesc:
* @author: cyjer
* @date: 2023/1/30 9:53
*/
@Component
@Slf4j
public class RedisRouteRepository implements RouteDefinitionRepository {
private static final String ROUTE_DEFINITION_REDIS_KEY_PREFIX_QUERY = "routeDefinition::";
private final ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate;
private final ReactiveValueOperations<String, RouteDefinition> routeDefinitionReactiveValueOperations;
public RedisRouteRepository(ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
this.reactiveRedisTemplate = reactiveRedisTemplate;
this.routeDefinitionReactiveValueOperations = reactiveRedisTemplate.opsForValue();
}
@Override
@Cacheable(cacheNames = "redisRoute")
public Flux<RouteDefinition> getRouteDefinitions() {
log.info("<<<<<<<<<<获取路由信息>>>>>>>>>>");
return this.reactiveRedisTemplate.keys(this.createKey("*")).flatMap((key) -> {
return this.reactiveRedisTemplate.opsForValue().get(key);
}).onErrorContinue((throwable, routeDefinition) -> {
if (log.isErrorEnabled()) {
log.error("get routes from redis error cause : {}", throwable.toString(), throwable);
}
});
}
@Override
@CacheEvict(cacheNames = "redisRoute")
public Mono<Void> save(Mono<RouteDefinition> route) {
log.info("<<<<<<<<<<保存路由信息>>>>>>>>>>");
return route.flatMap((routeDefinition) -> {
return this.routeDefinitionReactiveValueOperations.set(this.createKey(routeDefinition.getId()), routeDefinition).flatMap((success) -> {
return success ? Mono.empty() : Mono.defer(() -> {
return Mono.error(new RuntimeException(String.format("Could not add route to redis repository: %s", routeDefinition)));
});
});
});
}
@Override
@CacheEvict(cacheNames = "redisRoute")
public Mono<Void> delete(Mono<String> routeId) {
log.info("<<<<<<<<<<删除路由信息>>>>>>>>>>");
return routeId.flatMap((id) -> {
return this.routeDefinitionReactiveValueOperations.delete(this.createKey(id)).flatMap((success) -> {
return success ? Mono.empty() : Mono.defer(() -> {
return Mono.error(new NotFoundException(String.format("Could not remove route from redis repository with id: %s", routeId)));
});
});
});
}
private String createKey(String routeId) {
return ROUTE_DEFINITION_REDIS_KEY_PREFIX_QUERY + routeId;
}
}
/**
* @classDesc: 本地缓存
* @author: cyjer
* @date: 2023/1/30 9:53
*/
@Configuration
public class CaffeineConfig {
@Bean
@Primary
public CacheManager caffeineCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caches = new ArrayList<>();
Map<String, Object> map = getCacheType();
for (String name : map.keySet()) {
caches.add(new CaffeineCache(name, (Cache<Object, Object>) map.get(name)));
}
cacheManager.setCaches(caches);
return cacheManager;
}
/**
* 初始化自定义缓存策略
*
* @return
*/
private static Map<String, Object> getCacheType() {
Map<String, Object> map = new ConcurrentHashMap<>();
map.put("redisRoute", Caffeine.newBuilder().build());
return map;
}
}
到此其实基于redis的路由仓储service已经结束
基于zookeeper注册中心实现项目自动上报路由
为了实现动态路由,其实可以采用提供暴露接口的方式把相应的仓储接口提供出来,本示例提供的只是使用zk的监听机制,实现动态路由
关于zk的事件监听可以查看我的这篇文章:zookeeper相关操作
1、网关启动时添加zk监听事件
@Component
@Slf4j
@RequiredArgsConstructor
public class GatewayApplication implements ApplicationListener<ContextRefreshedEvent> {
private final RouteDefinitionService routeDefinitionService;
private final ZookeeperService zookeeperService;
private final RouteProcesser routeProcesser;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//拉取网关配置
routeDefinitionService.refreshRouteDefinition();
log.info("<<<<<<<<<<刷新网关配置完成>>>>>>>>>>");
zookeeperService.create(Constraint.ROUTE_DEFINITION, "init");
zookeeperService.addWatchChildListener(Constraint.ROUTE_DEFINITION, routeProcesser);
log.info("<<<<<<<<<<动态路由监听器配置完成>>>>>>>>>>");
}
}
2、发布刷新事件:
public void refreshRouteDefinition() {
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
redisRouteRepository.getRouteDefinitions();
}
3、 监听处理
/**
* @classDesc:
* @author: cyjer
* @date: 2023/2/10 11:13
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class RouteProcesser extends AbstractChildListenerProcess implements ApiDefinitionConstraint {
private static final String JTR = "/";
private final static String ROUTE_CONFIG_ARGS_PREFIX = "_genkey_";
private final RedisRouteRepository redisRouteRepository;
private final RouteDefinitionService routeDefinitionService;
@Override
public void process(CuratorFramework curatorFramework, PathChildrenCacheEvent cacheEvent) {
ChildData data = cacheEvent.getData();
if (Objects.nonNull(data) && cacheEvent.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)) {
log.info("<<<<<<<<<<监听到动态路由变动申请>>>>>>>>>>");
String content = new String(data.getData(), StandardCharsets.UTF_8);
SiriusRouteDefinition siriusRouteDefinition = JSONObject.parseObject(content, SiriusRouteDefinition.class);
RouteDefinition routeDefinition = new RouteDefinition();
String serviceId = siriusRouteDefinition.getServiceId();
routeDefinition.setId(serviceId);
URI routeUri = UriComponentsBuilder.fromUriString("lb://" + serviceId).build().toUri();
routeDefinition.setUri(routeUri);
List<PredicateDefinition> predicates = getPredicates(JTR + serviceId + "/**");
if (!predicates.isEmpty()) {
routeDefinition.setPredicates(predicates);
}
// 获取过滤器
List<FilterDefinition> filters = new ArrayList<>();
Map<String, String> args = new LinkedHashMap<>();
args.put(ROUTE_CONFIG_ARGS_PREFIX, "1");
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setName("StripPrefix");
filterDefinition.setArgs(args);
filters.add(filterDefinition);
routeDefinition.setFilters(filters);
redisRouteRepository.save(Mono.just(routeDefinition)).subscribe();
routeDefinitionService.refreshRouteDefinition();
log.info("<<<<<<<<<<刷新网关路由成功>>>>>>>>>>");
}
}
/**
* 获取地址断言
*
* @param routePaths 路由地址
* @return java.util.List
*/
private List<PredicateDefinition> getPredicates(String routePaths) {
Map<String, String> args = new HashMap<>();
String[] paths = routePaths.split(",");
for (int i = 0; i < paths.length; i++) {
args.put(ROUTE_CONFIG_ARGS_PREFIX + i, paths[i]);
}
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName("Path");
predicate.setArgs(args);
List<PredicateDefinition> predicates = new ArrayList<>();
predicates.add(predicate);
return predicates;
}
}
4、非网关的其他项目启动时注册路由
/**
* @classDesc: 注册应用路由信息
* @author: cyjer
* @date: 2023/2/11 20:47
*/
@Component
@Order(2)
@RequiredArgsConstructor
@Slf4j
public class ServiceRouteDefinitionReporter implements CommandLineRunner, Constraint {
private final GatewayServiceProperties gatewayServiceProperties;
private final ZookeeperService zookeeperService;
@Override
public void run(String... args) throws Exception {
String applicationName = apiDefinitionReporter.getApplicationName();
siriusRouteDefinition.setServiceId(applicationName);
siriusRouteDefinition.setGatewayId(gatewayServiceProperties.getGatewayId());
zookeeperService.create(ROUTE_DEFINITION + SPLIT + applicationName, JSONObject.toJSONString(siriusRouteDefinition));
zookeeperService.update(ROUTE_DEFINITION + SPLIT + applicationName, JSONObject.toJSONString(siriusRouteDefinition));
log.info("<<<<< successfully reported route information >>>>>");
}
}