springcloud-gateway 路由加载流程

问题

Spring Cloud Gateway版本是2.2.9.RELEASE,原本项目中依赖服务自动发现来自动配置路由到微服务的,但是发现将spring.cloud.gateway.discovery.locator.enabled=false 启动之后Gateway依然会将所有微服务自动注册到路由中,百思不得其解,遂调试代码观察期启动过曾,记录此文以供参考,官方文档
在这里插入图片描述
可以看到此处明确表示,可以通过设置该值来讲微服务中服务自动添加到配置中。按照这个线索,找到了
org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator#getRouteDefinitions
这个类在之前的文章includeExpression中有提及过,在构造函数中对成员变量

public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator {private final DiscoveryLocatorProperties properties;...private Flux<List<ServiceInstance>> serviceInstances;/*** Kept for backwards compatibility. You should use the reactive discovery client.* @param discoveryClient the blocking discovery client* @param properties the configuration properties* @deprecated kept for backwards compatibility*/@Deprecatedpublic DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient,DiscoveryLocatorProperties properties) {this(discoveryClient.getClass().getSimpleName(), properties);serviceInstances = Flux.defer(() -> Flux.fromIterable(discoveryClient.getServices())).map(discoveryClient::getInstances).subscribeOn(Schedulers.boundedElastic());}public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient,DiscoveryLocatorProperties properties) {this(discoveryClient.getClass().getSimpleName(), properties);serviceInstances = discoveryClient.getServices().flatMap(service -> discoveryClient.getInstances(service).collectList());}...

这里会对serviceInstances进行初始化,从注册中心获取所有已注册的微服务实例进行填充,serviceInstance的获取结束了,在DiscoveryClientRouteDefinitionLocator还有一个重要的方法

#DiscoveryClientRouteDefinitionLocator@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {SpelExpressionParser parser = new SpelExpressionParser();Expression includeExpr = parser.parseExpression(properties.getIncludeExpression());Expression urlExpr = parser.parseExpression(properties.getUrlExpression());Predicate<ServiceInstance> includePredicate;if (properties.getIncludeExpression() == null|| "true".equalsIgnoreCase(properties.getIncludeExpression())) {includePredicate = instance -> true;}else {includePredicate = instance -> {Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class);if (include == null) {return false;}return include;};}return serviceInstances.filter(instances -> !instances.isEmpty()).map(instances -> instances.get(0)).filter(includePredicate).map(instance -> {RouteDefinition routeDefinition = buildRouteDefinition(urlExpr,instance);final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties);for (PredicateDefinition original : this.properties.getPredicates()) {PredicateDefinition predicate = new PredicateDefinition();predicate.setName(original.getName());for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {String value = getValueFromExpr(evalCtxt, parser,instanceForEval, entry);predicate.addArg(entry.getKey(), value);}routeDefinition.getPredicates().add(predicate);}for (FilterDefinition original : this.properties.getFilters()) {FilterDefinition filter = new FilterDefinition();filter.setName(original.getName());for (Map.Entry<String, String> entry : original.getArgs().entrySet()) {String value = getValueFromExpr(evalCtxt, parser,instanceForEval, entry);filter.addArg(entry.getKey(), value);}routeDefinition.getFilters().add(filter);}return routeDefinition;});}

根据includeExpr 配置的sl表达式判断当前的serviceInstance是否服务转发要求。填充好predicate,filter之后返回routeDefinition,urlExpr的默认值是"lb://"+serverId,predicate匹配的path就等于serverId,有配置可以转换为小写spring.cloud.gateway.discovery.locator.lower-case-service-id=true因为eureka默认会uppercase serverId,filter中会StripPrefix=1,将路径中的serverId移除再转发给下游。路由定义的部分就这里了,他的刷新逻辑,依靠SpringRefreshContext 事件,具体的定义逻辑在org.springframework.cloud.gateway.config.GatewayAutoConfiguration

	@Bean@ConditionalOnClass(name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor")public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {return new RouteRefreshListener(publisher);}

RouteRefreshListener 实现了ApplicationListener<ApplicationEvent>,监控以下几个事件,并按照条件进行逻辑执行

  • ContextRefreshEvent 启动容器,或者beans重新加载
  • RefreshScopeRefreshdEvent 是 Spring Cloud 中的一个事件,当 @RefreshScope 注解的 beans 被重新加载时发布。这个事件主要用于 Spring Cloud 的配置刷新机制,当配置中心的配置信息发生变化时,会触发该事件以刷新 beans。
  • ParentHeartbeatEvent 是 Spring Cloud Bus 中的一个事件,用于在父上下文(通常是 Spring Cloud Config Server)发生心跳时发布。这个事件用于通知子上下文(如微服务)关于父上下文的健康状态。
  • HeartbeatEvent 是 Spring Cloud Bus 中的另一个事件,用于在服务实例之间广播心跳消息,以检测和维持各个服务的健康状态。
	public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ContextRefreshedEvent) {ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event;if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) {reset();}}else if (event instanceof RefreshScopeRefreshedEvent|| event instanceof InstanceRegisteredEvent) {reset();}else if (event instanceof ParentHeartbeatEvent) {ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;resetIfNeeded(e.getValue());}else if (event instanceof HeartbeatEvent) {HeartbeatEvent e = (HeartbeatEvent) event;resetIfNeeded(e.getValue());}}

容器启动完成之后,该监听器接收到ContextRefreshedEvent ,会执行reset方法,reset方法主要功能是发送一个RefreshRoutesEvent 事件,定义如下

public class RefreshRoutesEvent extends ApplicationEvent {public RefreshRoutesEvent(Object source) {super(source);}
}

同样在AutoConfig中声明的CachingRouteLocator接收该参数,

tip:另外还有个CachingRouteDefinitionLocator也生命监听 RefreshRoutesEvent事件,但是默认配置下并不会产生任何逻辑

	@Bean@Primary@ConditionalOnMissingBean(name = "cachedCompositeRouteLocator")// TODO: property to disable composite?public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators)));}

event invoke方法如下

	public void onApplicationEvent(RefreshRoutesEvent event) {try {fetch().collect(Collectors.toList()).subscribe(list -> Flux.fromIterable(list).materialize().collect(Collectors.toList()).subscribe(signals -> {applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this));cache.put(CACHE_KEY, signals);}, throwable -> handleRefreshError(throwable)));}catch (Throwable e) {handleRefreshError(e);}}

CompositeRouteLocator的目的是混合 多个来源的Routers定义比如配置文件,接口,注册中心,通过@Bean形式定义的路由。可以理解成mixed route locator。
fetch方法就是获取所有RouteLocator的路由定义
private Flux<Route> fetch() { return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE); }
这里其实就是指定,之前org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator#getRouteDefinitions的方法也就是获取具体的路由定义,获取成功之后放入缓存,Fire RefreshRoutesResultEvent事件就解决了,开发人员可以通过 监听RefreshRoutesResultEvent 主动感知当前的路由变化。路由加载流程到此结束,

后语

我的问题是通过设置spring.cloud.gateway.discovery.locator.enabled依然没有办法让微服务注册失效,在整个流程中并没有看到使用该数值的地方,难道这是一个bug?转念一想DiscoveryClientRouteDefinitionLocator只需要阻止该Bean的注册即可取消掉从eureka获取服务实例的能力,通过引用关系可知

  • org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration.ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration
  • org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration.BlockingDiscoveryClientRouteDefinitionLocatorConfiguration

这里两处找到了其定义,一个是为reactive非阻塞io准备的,一个是为阻塞io准备的,根据源代码可知,具体执行提供那个Locator取决于spring.cloud.discovery.reactive.enabled,我的项目中是通过VM Options参数的形式传入的-Dspring.cloud.config.discovery.enabled=true,所以我在这两个方法处都打了断点。

		@Configuration(proxyBeanMethods = false)@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",matchIfMissing = true)public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {@Bean@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient,DiscoveryLocatorProperties properties) {return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);}}@Configuration(proxyBeanMethods = false)@Deprecated@ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled",havingValue = "false")public static class BlockingDiscoveryClientRouteDefinitionLocatorConfiguration {@Bean@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);}}

当我准备要执行调试模式想看看具体是哪个Bean被初始化的时候,恍然大悟看到了DiscoveryClientRouteDefinitionLocator的依赖,我自己在GatewayApplication 中手动注册了Bean

    @Beanpublic DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator(ReactiveDiscoveryClient discoveryClient,DiscoveryLocatorProperties discoveryLocatorProperties) {return new DiscoveryClientRouteDefinitionLocator(discoveryClient, discoveryLocatorProperties);}

真的是笑死,出这个问题的原因是因为最开始的代码使用的出c4o老师生成的,一开始并不明确该代码的作用,后面查看文档才知道用于微服务发现。已经不在关注这里了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/862656.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【仿真建模-anylogic】开发规范

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-28 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 0. 说明 实际模型开发过程中&#xff0c;对遇到的问题进行总结归纳出以下开发规范&#xff0c;仅供参考&#xff01; 1. 强制性规范 1…

pcr实验室和P2实验室装修设计中的区别

PCR实验室和P2实验室在装修设计的区别是什么&#xff1f;PCR实验室指的是基因扩增实验室&#xff0c;而P2实验室是指生物安全实验室中的一个分类&#xff0c;是生物安全防护达到二级的实验室。那么PCR实验室和P2实验室装修设计标准是什么&#xff1f;实验室装修公司小编为您详解…

数据分析三剑客-Matplotlib

数据分析三剑客 数据分析三剑客通常指的是在Python数据分析领域中&#xff0c;三个非常重要的工具和库&#xff1a;Pandas、NumPy和Matplotlib。Pandas主要负责数据处理和分析&#xff0c;NumPy专注于数值计算和数学运算&#xff0c;而Matplotlib则负责数据可视化。这三个库相…

动手学深度学习(Pytorch版)代码实践 -计算机视觉-44目标检测算法综述:R-CNN、SSD和YOLO

41~44目标检测算法综述&#xff1a;R-CNN、SSD和YOLO 1. 区域卷积神经网络 (R-CNN 系列) 1.1 R-CNN 使用启发式搜索算法来选择锚框。使用预训练模型对每个锚框提取特征&#xff08;每个锚框视为一张图片&#xff0c;使用 CNN 提取特征&#xff09;。训练 SVM 进行类别分类&a…

计算机体系结构 量化研究方法

在第一章中看到关于微处理器中dynamic energy 和 dynamic power的定义觉得有些奇怪&#xff0c;特别记录一下。 上面的定义是取决于上下文的&#xff1a;动态能量可以理解为在一个时钟周期内&#xff0c;由电容充放电消耗的能量总和&#xff0c;而动态功率则是这种能量消耗在单…

Vite脚手架+Vant组件库初始化前端项目

脚手架概念&#xff1a; 在前端开发中&#xff0c;脚手架&#xff08;Scaffold&#xff09;是指一个用于快速搭建项目基础结构的工具或模板。脚手架包含了项目所需的基本文件结构、配置文件、依赖管理等内容&#xff0c;使开发者能够更快速地开始项目开发&#xff0c;而不必从…

常微分方程算法之编程示例四(龙格-库塔法)

目录 一、算例一 1.1 研究问题 1.2 C++代码 1.3 计算结果 二、算例二 2.1 研究问题 2.2 C++代码 2.3 计算结果 一、算例一 本节我们采用龙格-库塔法(Runge-Kutta法)求解算例。 龙格-库塔法的原理及推导请参考: 常微分方程算法之龙格-库塔法(Runge-Kutta法)…

经验分享之会员 SaaS 系统

前言 2018年&#xff0c;这是不平凡的一年&#xff1b;互联网行业的中台战略、会员经济等模式如火如荼&#xff0c;同时也逐渐地走入我们公司每个人的视野。在南海集团的战略规划背景下&#xff0c;当时我所在的公司作为集团的研发中心&#xff0c;承担了对会员 SaaS 系统的建…

【小程序静态页面】猜拳游戏大转盘积分游戏小程序前端模板源码

猜拳游戏大转盘积分游戏小程序前端模板源码&#xff0c; 一共五个静态页面&#xff0c;首页、任务列表、大转盘和猜拳等五个页面。 主要是通过做任务来获取积分&#xff0c;积分可以兑换商品&#xff0c;也可用来玩游戏&#xff1b;通过玩游戏既可能获取奖品或积分也可能会消…

赛盈分销谈烧烤市场的未来,论创新与差异化出海的重要性!

过去几年里&#xff0c;海外旅游市场格外热闹&#xff0c;不仅景点火爆&#xff0c;露营烧烤活动也更加频繁&#xff0c;而这也催生了烧烤设备的市场增长。 全球烧烤用具市场在去年就达到了265.8亿美元&#xff0c;同比增长20.3%&#xff0c;Statista数据预测到2027年将超过515…

MySQL事务——Java全栈知识(31)

1、事务的特性 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是不可分割的最小操作单元&#xff0c;要么全部成功&#xff0c;要么全部失败。 一致性&#xff08;Consistency&#xff09;&#xff1a;事务完成时&#xff0c;必须使所有的数据都保持一致状态。 隔离…

typescript学习回顾(四)

今天来分享下ts中的类&#xff0c;关于ts中的类的概念&#xff0c;面向对象的一种思想&#xff0c;以及类里面的一些属性成员&#xff0c;一些基础的用法&#xff0c;后面会有一个小练习。 类 基本概念 我的理解&#xff1a;类是编程语言中面向对象的一种思想&#xff0c;一…

ChatTTS超真实自然的语音合成模型

项目介绍 ChatTTS是一款专为优化对话场景而生的语音生成模型&#xff0c;尤其匹配大型语言模型&#xff08;LLM&#xff09;的交互需求&#xff0c;以及生成对话式音频、视频旁白等应用场景&#xff0c;无缝覆盖中英文双语。 通过汲取约100,000小时的高质量中英语音数据进行深…

RedHat9 | podman容器

1、容器技术介绍 传统问题 应用程序和依赖需要一起安装在物理主机或虚拟机上的操作系统应用程序版本比当前操作系统安装的版本更低或更新两个应用程序可能需要某一软件的不同版本&#xff0c;彼此版本之间不兼容 解决方式 将应用程序打包并部署为容器容器是与系统的其他部分…

鸿蒙北向开发 ubuntu20.04 gn + ninja环境傻瓜式搭建闭坑指南

ninja跟gn都是比较时髦的东西,由歪果仁维护,如果走下载源码并编译这种流程的话需要走github跟google官网下载,国内的用网环境相信各位傻瓜都知道,github跟google这几年基本是属于连不上的状态,好在你看的鸿蒙项目跟国内的一些软件大厂已经帮你爬过梯子了,ninja工具跟gn工具已经…

一文弄懂梯度下降算法

1、引言 在上一篇文章中&#xff0c;我们介绍了如何使用线性回归和成本损失函数为房价数据找到最拟合的线。不过&#xff0c;我们也看到&#xff0c;测试多个截距值可能既繁琐又低效。在本文中&#xff0c;我们将深入探讨梯度下降算法&#xff0c;这是一种更加强大的技术&…

go~缓存设计配合singleFlight

一个缓存设计&#xff0c;配合go的singleFlight 最开始的设计如下 添加分布式缓存 上线后分布式缓存上涨的流量并不等于下游下降的流量&#xff0c;而是下游下降的流量 * 2&#xff5e;3 究其原因&#xff0c;就是采用了go的singleFlight&#xff0c;假定请求缓存时长10ms&a…

学校消防巡检二维码卡如何使用

为了加强和规范学校消防安全管理&#xff0c;预防和减少火灾危害&#xff0c;保障公共财产和教师、职工、学生的生命安全&#xff1b;传统的学校消防巡检都是纸质巡检记录会存在浪费纸张、信息滞后、容易造假、容易丢失、信息更新不及时。 通过凡尔码平台生成学校消防二维码巡…

揭秘:国产化系统加密

随着信息化时代的到来&#xff0c;数据安全已成为国家、企业和个人共同关注的焦点。政府系统国产化作为提高信息安全和自主可控能力的重要举措&#xff0c;正逐步得到广泛实施。在这一过程中&#xff0c;沙箱技术作为一种先进的安全机制&#xff0c;其在国产化系统加密效果方面…

系统设计中的垂直扩展和水平扩展,该如何选择?

在系统设计时&#xff0c;我们需要预估系统的容量&#xff0c;但是&#xff0c;随着系统流量的增大&#xff0c;我们会通过扩容来应对流量&#xff0c;常见的扩容方式是垂直扩展和水平扩展&#xff0c;因此&#xff0c;这本文章&#xff0c;我们将深入探讨这两种扩展策略的优缺…