前言

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 reactiveRedisTemplate;

private final ReactiveValueOperations routeDefinitionReactiveValueOperations;

public RedisRouteRepository(ReactiveRedisTemplate reactiveRedisTemplate) {

this.reactiveRedisTemplate = reactiveRedisTemplate;

this.routeDefinitionReactiveValueOperations = reactiveRedisTemplate.opsForValue();

}

@Override

@Cacheable(cacheNames = "redisRoute")

public Flux 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 save(Mono 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 delete(Mono 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 caches = new ArrayList<>();

Map map = getCacheType();

for (String name : map.keySet()) {

caches.add(new CaffeineCache(name, (Cache) map.get(name)));

}

cacheManager.setCaches(caches);

return cacheManager;

}

/**

* 初始化自定义缓存策略

*

* @return

*/

private static Map getCacheType() {

Map 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 {

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 predicates = getPredicates(JTR + serviceId + "/**");

if (!predicates.isEmpty()) {

routeDefinition.setPredicates(predicates);

}

// 获取过滤器

List filters = new ArrayList<>();

Map 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 getPredicates(String routePaths) {

Map 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 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 >>>>>");

}

}

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: