写点什么

如何实现 Spring Gateway 路由的动态加载和刷新?

  • 2022 年 3 月 30 日
  • 本文字数:3694 字

    阅读完需:约 12 分钟

如何实现Spring Gateway 路由的动态加载和刷新?

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


1、前言

在微服务化过程中,客户端可能需要调用多个服务的接口才能完成一个业务需求。网关作为后

端微服务的统一入口,利用路由转发可以方便客户端快速接入各个微服务。

Spring Gateway (以下简称 gateway)路由配置默认情况下是写在配置文件中的,有一个新的

服务接入时,需要修改配置文件,然后重启网关才能生效。然而目前更多的需求是通过提供可

视化页面,在前端页面增删改来使网关路由动态生效。

结合实际业务需求,接下来从原理和实践两个方面介绍如何上述需求。

2、原理

我们分两部分来说,先说动态加载,再说动态刷新。

2.1 动态加载

要想实现动态加载,我们要先弄清楚 gateway 是如何加载现有的配置信息的。

目前配置路由主要有两种方式,一种是用 yml 配置文件,一种是写代码里。

# yml配置文件形式spring:		cloud:				gateway:						routes:						- id: v1									uri: http://xxx/v1									predicates:									- Path=/xxx
复制代码


// 代码形式@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {	return builder.routes() 		.route("v1", r -> r.path("/xxx") 					.uri("http://xxx/v1"))		 .build();}
复制代码

无论是 yml 还是代码,这些配置最终都是被封装到 RouteDefinition 对象中的。所有路由信息

在系统启动时就被加载装配好了,并存到了内存里。

参考代码如下:

//GatewayAutoConfiguration@Bean@ConditionalOnMissingBeanpublic PropertiesRouteDefinitionLocatorpropertiesRouteDefinitionLocator(GatewayProperties properties) {		return new PropertiesRouteDefinitionLocator(properties);}
@Bean@ConditionalOnMissingBean(RouteDefinitionRepository.class)public InMemoryRouteDefinitionRepositoryinMemoryRouteDefinitionRepository() { return new InMemoryRouteDefinitionRepository();}//PropertiesRouteDefinitionLocatorpublic class PropertiesRouteDefinitionLocator implementsRouteDefinitionLocator { private final GatewayProperties properties; ... @Override public Flux<RouteDefinition> getRouteDefinitions() { return Flux.fromIterable(this.properties.getRoutes()); }}
复制代码

重点类为 RouteDefinitionLocator,此接口有多个实现类,分别对应不同方式配置的路由方

式。其中 RouteDefinitionRepository 是从存储器中(例如:mysql、redis 等)读取

RouteDefinition。可以结合 RouteDefinitionRepository 来实现动态路由加载。其他几种获

取路由方式不是本文重点,暂不详说。

至此,我们就可以通过可视化页面修改数据库(此步骤为单纯的数据库增删改),在网关启动时

加载数据库数据,并通过 RouteDefinitionRepository 的实现类

InMemoryRouteDefinitionRepository 加载路由信息了。

2.2 动态刷新

通过 2.1 我们找到了 gateway 从数据库加载路由的方式,但是还停留在 gateway 启动时加载数据

的方式。接下来就看下如何实现在用户操作数据的时候就能完成 gateway 路由的刷新。

其实 gateway 是提供了刷新的事件的,那就是 RefreshRoutesEvent

参考代码如下:

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);		} }
复制代码

我们再来深挖下触发了 RefreshRoutesEvent 事件后,gateway 是如何处理的。

@Overridepublic void onApplicationEvent(ApplicationEvent event) { 	...	else if (event instanceof RefreshRoutesEvent && routeLocator != null) {
// forces initialization routeLocator.ifAvailable(locator ->locator.getRoutes().subscribe()); }}
复制代码

所以,我们的思路是,在用户操作路由数据,在数据保存到数据库之后,代码向 gateway 发送

刷新事件就可以了。

3、实践

通过原理的分析,了解了网关从存储器中加载和动态刷新的机制。我们来整理下思路。

  • 想加载路由数据到 gateway 内存中,需要调用 InMemoryRouteDefinitionRepository 的 save()方法。

  • 想触发 gateway 的动态刷新,需要调用 RefreshRoutesEvent 事件。

接下来从这两点出发,一起开启实践之旅。

3.1 动态加载

动态加载按照顺序可以分为三步,加载路由数据、组装路由、保存到 gateway 内存中。

3.1.1 加载数据库数

gateway 可以通过 feign 调用的方式读取数据,此步骤比较简单,不做详细的描述。

List<GatewayRoute> routeList =gatewayServiceClient.getApiRouteList().getData();
复制代码
3.1.2 组装路由、保存到 gateway 内存

刚刚也分析到了 RouteDefinition 是路由数据的核心。它大体包括三个部分,基本信息、断言

(PredicateDefinition)、过滤器(FilterDefinition)。

接下来根据查询到的路由数据,进行 gateway 路由数据封装和保存。

//路由封装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 动态刷新

动态刷新分为两步。第一步是提供公共方法,允许业务侧更新数据库之后调用刷新网关事件。

第二步在 gateway 端接收到刷新网关事件后,调用 RefreshRoutesEvent 事件实现刷新。

3.2.1 公共 gateway 刷新方法

参考代码如下

public class OpenRestTemplate extends RestTemplate {		/**		* 刷新网关		*/	public void refreshGateway() {		publisher.publishEvent(			new RefreshRemoteApplicationEvent(this,busProperties.getId(),null)		);	}}
复制代码

3.2.2 调用 RefreshRoutesEvent 事件实现刷新

gateway 接收到业务侧路由刷新事件后,调用 RefreshRoutesEvent 即可完成刷新动作。

参考代码如下

/**	* 接收业务侧刷新事件	*	* @param event	*/@Overridepublic void onApplicationEvent(RefreshRemoteApplicationEvent event) {	refresh();}
/** * 刷新路由 * * @return */public Mono<Void> refresh() { //3.1加载路由的逻辑 this.loadRoutes(); //触发默认路由刷新事件,刷新缓存路由 this.publisher.publishEvent(new RefreshRoutesEvent(this)); return Mono.empty();}
复制代码

4、结论

结合实际业务需求,从原理和实践两个方面介绍了实现网关路由的动态加载和刷新方式。至此

动态加载和刷新的功能就实现了,通过此功能,无论开发、测试、生产环境,都可以通过可视

化页面快速的实现路由的动态修改,在配置速度和准确率上都可以得到很大提升。


关于领创集团(Advance Intelligence Group)

领创集团成立于 2016 年,致力于通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈。集团旗下包含企业业务和消费者业务两大板块,企业业务包含 ADVANCE.AI 和 Ginee,分别为银行、金融、金融科技、零售和电商行业客户提供基于 AI 技术的数字身份验证、风险管理产品和全渠道电商服务解决方案;消费者业务 Atome Financial 包括亚洲领先的先享后付平台 Atome 和数字金融服务。2021 年 9 月,领创集团宣布完成超 4 亿美元 D 轮融资,融资完成后领创集团估值已超 20 亿美元,成为新加坡最大的独立科技创业公司之一。


往期回顾 BREAK AWAY

如何解决海量数据更新场景下的 Mysql 死锁问题

企业级 APIs 安全实践指南 (建议初中级工程师收藏)

Cypress UI 自动化测试框架


▼ 如果觉得这篇内容对你有所帮助,有所启发,欢迎点赞收藏:

1、点赞、关注领创集团,获取最新技术分享和公司动态。

2、关注我们的公众号 & 知乎号「领创集团 Advance Group」或访问官方网站,了解更多企业动态。

发布于: 刚刚阅读数: 2
用户头像

智慧领创美好生活 2021.08.12 加入

AI技术驱动的科技集团,致力于以技术赋能为核心,通过科技创新的本地化应用,改造和重塑金融和零售行业,以多元化的业务布局打造一个服务于消费者、企业和商户的生态圈,带来个性化、陪伴式的产品服务和优质体验。

评论

发布
暂无评论
如何实现Spring Gateway 路由的动态加载和刷新?_微服务_领创集团Advance Intelligence Group_InfoQ写作平台