当前位置: 首页 > news >正文

聊聊Spring Cloud Gateway 动态路由及通过Apollo的实现

这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

背景

在之前我们了解的Spring Cloud Gateway配置路由方式有两种方式

  1. 通过配置文件
spring:
  cloud:
    gateway:
      routes:
        - id: test
          predicates:
            - Path=/ms/test/*
          filters:
            - StripPrefix=2
          uri: http://localhost:9000
  1. 通过JavaBean
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/ms/test/**")
                .filters(f -> f.stripPrefix(2))
                .uri("http://localhost:9000"))
                .build();
    }

但是遗憾的是这两种方式都不支持动态路由,都需要重启服务。
所以我们需要对Spring Cloud Gateway进行改造,在改造的时候我们就需要看看源码了解下Spring Cloud Gateway的路由加载

路由的加载

我们之前分析了路由的加载主要在GatewayAutoConfigurationrouteDefinitionRouteLocator方法加载的

在这里插入图片描述

实际上最终获取的路由信息都是在GatewayProperties这个配置类中
在这里插入图片描述

在这里插入图片描述

所以我们在动态路由的时候修改GatewayProperties中的属性即可,即

  1. List<RouteDefinition> routes
  2. List<FilterDefinition> defaultFilters

恰巧Spring Cloud Gateway也提供了相应的getset方法

在这里插入图片描述

实际如果我们修改了该属性我们会发现并不会立即生效,因为我们会发现还有一个RouteLocator就是CachingRouteLocator,并且在配置Bean的时候加了注解@Primary,说明最后使用额RouteLocator实际是CachingRouteLocator
在这里插入图片描述
CachingRouteLocator最后还是使用RouteDefinitionRouteLocator类加载的,也是就我们上面分析的,看CachingRouteLocator就知道是缓存作用

在这里插入图片描述
这里引用网上一张加载图片

在这里插入图片描述

参考 https://www.jianshu.com/p/490739b183af

所以看到这里我们知道我们还需要解决的一个问题就是更新缓存,如何刷新缓存呢,这里Spring Cloud Gateway利用spring的事件机制给我提供了扩展
在这里插入图片描述

在这里插入图片描述
所以我们要做的事情就是这两件事:

  1. GatewayProperties
  2. 刷新缓存

实现动态路由

这里代码参考 https://github.com/apolloconfig/apollo-use-cases

@Component
@Slf4j
public class GatewayPropertiesRefresher implements ApplicationContextAware, ApplicationEventPublisherAware {
	
	private static final String ID_PATTERN = "spring\\.cloud\\.gateway\\.routes\\[\\d+\\]\\.id";

	private static final String DEFAULT_FILTER_PATTERN = "spring\\.cloud\\.gateway\\.default-filters\\[\\d+\\]\\.name";

	private ApplicationContext applicationContext;

	private ApplicationEventPublisher publisher;

	@Autowired
	private GatewayProperties gatewayProperties;

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}


	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.publisher = applicationEventPublisher;
	}

	@ApolloConfigChangeListener(value = "route.yml",interestedKeyPrefixes = "spring.cloud.gateway.")
	public void onChange(ConfigChangeEvent changeEvent) {
		refreshGatewayProperties(changeEvent);
	}

	/***
	 * 刷新org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator中定义的routes
	 *
	 * @param changeEvent
	 * @return void
	 * @author ksewen
	 * @date 2019/5/21 2:13 PM
	 */
	private void refreshGatewayProperties(ConfigChangeEvent changeEvent) {
		log.info("Refreshing GatewayProperties!");
		preDestroyGatewayProperties(changeEvent);
		this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
		refreshGatewayRouteDefinition();
		log.info("GatewayProperties refreshed!");
	}

	/***
	 * GatewayProperties没有@PreDestroy和destroy方法
	 * org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#rebind(java.lang.String)中destroyBean时不会销毁当前对象
	 * 如果把spring.cloud.gateway.前缀的配置项全部删除(例如需要动态删除最后一个路由的场景),initializeBean时也无法创建新的bean,则return当前bean
	 * 若仍保留有spring.cloud.gateway.routes[n]或spring.cloud.gateway.default-filters[n]等配置,initializeBean时会注入新的属性替换已有的bean
	 * 这个方法提供了类似@PreDestroy的操作,根据配置文件的实际情况把org.springframework.cloud.gateway.config.GatewayProperties#routes
	 * 和org.springframework.cloud.gateway.config.GatewayProperties#defaultFilters两个集合清空
	 *
	 * @param
	 * @return void
	 * @author ksewen
	 * @date 2019/5/21 2:13 PM
	 */
	private synchronized void preDestroyGatewayProperties(ConfigChangeEvent changeEvent) {
		log.info("Pre Destroy GatewayProperties!");
		final boolean needClearRoutes = this.checkNeedClear(changeEvent, ID_PATTERN, this.gatewayProperties.getRoutes()
				.size());
		if (needClearRoutes) {
			this.gatewayProperties.setRoutes(new ArrayList<>());
		}
		final boolean needClearDefaultFilters = this.checkNeedClear(changeEvent, DEFAULT_FILTER_PATTERN, this.gatewayProperties.getDefaultFilters()
				.size());
		if (needClearDefaultFilters) {
			this.gatewayProperties.setDefaultFilters(new ArrayList<>());
		}
		log.info("Pre Destroy GatewayProperties finished!");
	}

	private void refreshGatewayRouteDefinition() {
		log.info("Refreshing Gateway RouteDefinition!");
		this.publisher.publishEvent(new RefreshRoutesEvent(this));
		log.info("Gateway RouteDefinition refreshed!");
	}

	/***
	 * 根据changeEvent和定义的pattern匹配key,如果所有对应PropertyChangeType为DELETED则需要清空GatewayProperties里相关集合
	 *
	 * @param changeEvent
	 * @param pattern
	 * @param existSize
	 * @return boolean
	 * @author ksewen
	 * @date 2019/5/23 2:18 PM
	 */
	private boolean checkNeedClear(ConfigChangeEvent changeEvent, String pattern, int existSize) {
		return changeEvent.changedKeys().stream().filter(key -> key.matches(pattern))
				.filter(key -> {
					ConfigChange change = changeEvent.getChange(key);
					return PropertyChangeType.DELETED.equals(change.getChangeType());
				}).count() == existSize;
	}
}

然后我们在apollo添加namespace:route.yml

配置内容如下:

spring:
  cloud:
    gateway:
      routes:
        - id: test
          predicates:
            - Path=/ms/test/*
          filters:
            - StripPrefix=2
          uri: http://localhost:9000

然后我们可以通过访问地址:
http:localhost:8080/ms/test/health

看删除后是否是404,加上后是否可以正常动态路由

值得注意的是上面@ApolloConfigChangeListener中如果没有添加新的namespacevalue可以不用填写,如果配置文件是yml配置文件,在监听的时候需要指定文件后缀

相关文章:

  • Python爬虫入狱小技巧
  • java中判断集合是否为空
  • Vitepress搭建组件库文档(下)—— 组件 Demo
  • 计算多张图片的移位距离
  • 一起啃西瓜书(四)
  • 贪婪算法(Huffman编码)
  • 在Windows使用VSCode搭建嵌入式Linux开发环境
  • 嵌入式C语言编程中经验教训总结(七)指针、指针数组和数组指针
  • 表哥月薪22k+,而我还在混日子……
  • 【饭谈】在学习测开网课之前,你的心脏需要武装一下
  • Jetson Agx Xavier平台ov5693 glass-to-glass 延时测试
  • C++ 命名类型转换
  • 【定制项目】【M15 消防安全宣传】【横屏版】主要模块:视频 + 音频 + 图标 + 问答游戏
  • 在 Linux 中使用 tcp 转储命令来分析网络
  • 结合viewBinding实现RecyclerView组件的滚动列表显示
  • 【跃迁之路】【699天】程序员高效学习方法论探索系列(实验阶段456-2019.1.19)...
  • 2018天猫双11|这就是阿里云!不止有新技术,更有温暖的社会力量
  • canvas绘制圆角头像
  • Codepen 每日精选(2018-3-25)
  • Docker下部署自己的LNMP工作环境
  • java中具有继承关系的类及其对象初始化顺序
  • Linux各目录及每个目录的详细介绍
  • storm drpc实例
  • Vue 2.3、2.4 知识点小结
  • webgl (原生)基础入门指南【一】
  • 第13期 DApp 榜单 :来,吃我这波安利
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 基于webpack 的 vue 多页架构
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 浅谈Kotlin实战篇之自定义View图片圆角简单应用(一)
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 微信小程序填坑清单
  • 小程序开发之路(一)
  • 一个SAP顾问在美国的这些年
  • k8s使用glusterfs实现动态持久化存储
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # Java NIO(一)FileChannel
  • # 飞书APP集成平台-数字化落地
  • ###项目技术发展史
  • #git 撤消对文件的更改
  • (¥1011)-(一千零一拾一元整)输出
  • (2.2w字)前端单元测试之Jest详解篇
  • (3)llvm ir转换过程
  • (C++17) optional的使用
  • (floyd+补集) poj 3275
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (论文阅读30/100)Convolutional Pose Machines
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (四)库存超卖案例实战——优化redis分布式锁
  • (已解决)报错:Could not load the Qt platform plugin “xcb“
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)程序员技术练级攻略
  • .bat批处理出现中文乱码的情况