导语:Spring Cloud是一套优秀的微服务解决方案,堪称微服务架构集大成者。然而官方提供的API网关中,没有实现动态更新路由这个对大型复杂系统非常重要的功能。在此我们提供了一个完整的技术实现方式,希望能为有类似需求的同仁提供参考。

前言

原理
# yml配置文件形式
spring:
cloud:
gateway:
routes:
- id: v1
uri: http://xxx/v1
predicates:
- Path=/xxx
复制
// 代码形式
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("v1", r -> r.path("/xxx")
.uri("http://xxx/v1"))
.build();
}
复制
//GatewayAutoConfiguration
@Bean
@ConditionalOnMissingBean
public PropertiesRouteDefinitionLocator
propertiesRouteDefinitionLocator(
GatewayProperties properties) {
return new PropertiesRouteDefinitionLocator(properties);
}
@Bean
@ConditionalOnMissingBean(RouteDefinitionRepository.class)
public InMemoryRouteDefinitionRepository
inMemoryRouteDefinitionRepository() {
return new InMemoryRouteDefinitionRepository();
}
//PropertiesRouteDefinitionLocator
public class PropertiesRouteDefinitionLocator implements
RouteDefinitionLocator {
private final GatewayProperties properties;
...
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.properties.getRoutes());
}
}
复制
这样,我们就可以在网关启动时从一个动态数据源(数据库等,而非代码和配置文件)加载配置数据,并通过 RouteDefinitionRepository 的实现类 InMemoryRouteDefinitionRepository 加载为路由信息了。
public class RefreshRoutesEvent extends ApplicationEvent {
**
* Create a new ApplicationEvent.
* @param source the object on which the event initially occurred
(never {@code null})
*/
public RefreshRoutesEvent(Object source) {
super(source);
}
}
复制
@Override
public void onApplicationEvent(ApplicationEvent event) {
...
else if (event instanceof RefreshRoutesEvent && routeLocator != null)
{
forces initialization
routeLocator.ifAvailable(locator ->
locator.getRoutes().subscribe());
}
}
复制
实践

想加载路由数据到gateway内存中,需要调用InMemoryRouteDefinitionRepository 的save()方法。
想触发gateway的动态刷新,需要调用RefreshRoutesEvent事件。
List<GatewayRoute> routeList =
gatewayServiceClient.getApiRouteList().getData();
复制
//路由封装
routeList.forEach(gatewayRoute -> {
RouteDefinition结构
RouteDefinition definition = new RouteDefinition();
设置基本信息
definition.setId(gatewayRoute.getRouteName());
definition.setUri(UriComponentsBuilder.fromUriString("lb://" +gatewayRoute.getServiceId()).build().toUri());
//设置断言信息
List<PredicateDefinition> predicates = Lists.newArrayList();
PredicateDefinition predicatePath = new PredicateDefinition();
Map<String, String> predicatePathParams = new HashMap<>(8);
predicatePath.setName("Path");
predicatePathParams.put("name", gatewayRoute.getRouteName());
predicatePathParams.put("pattern", gatewayRoute.getPath());
predicatePathParams.put("pathPattern", gatewayRoute.getPath());
predicatePath.setArgs(predicatePathParams);
predicates.add(predicatePath);
definition.setPredicates(predicates);
//设置过滤器信息
List<FilterDefinition> filters = filters(gatewayRoute);
if (!CollectionUtils.isEmpty(filters)) {
definition.setFilters(filters);
}
//重点,保存到gateway内存,其实是放在了Map<String, RouteDefinition> routes里 this.repository.save(Mono.just(definition)).subscribe();
});
复制
3.2.1 公共gateway刷新方法
public class OpenRestTemplate extends RestTemplate {
/**
* 刷新网关
*/
public void refreshGateway() {
publisher.publishEvent(
new RefreshRemoteApplicationEvent(this,busProperties.getId(),null)
);
}
}
复制
/**
* 接收业务侧刷新事件
*
* @param event
*/
@Override
public void onApplicationEvent(RefreshRemoteApplicationEvent event) {
refresh();
}
/**
* 刷新路由
*
* @return
*/
public Mono<Void> refresh() {
//3.1加载路由的逻辑
this.loadRoutes();
//触发默认路由刷新事件,刷新缓存路由
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return Mono.empty();
}
复制
结论

感谢阅读「技术创想」第48期文章
领创集团正在春季招聘中
期待你的加入
点击文末
“阅读原文”
获取更多
招聘信息
关于领创集团
领创集团成立于 2016年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的先享后付平台 Atome 和数字金融服务。2021年 9月,领创集团宣布完成超4亿美元 D 轮融资,融资完成后领创集团估值已超 20亿美元,成为新加坡最大的独立科技创业公司之一。
文章转载自领创集团Advance Group,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。