网关层针对各微服务动态修改Ribbon路由策略
目录
一、介绍
二、常规的微服务设置路由算法方式
三、通过不懈努力,找到解决思路
四、验证
五、总结
一、介绍
最近,遇到这么一个需求:
1、需要在网关层(目前使用zuul)为某一个服务指定自定义算法IP Hash路由策略
2、除第一次改造重启后,后续为微服务添加路由算法时,zuul网关不能重启,因为会导致用户短时间内不会使用,也就是说,需要动态的为服务修改路由算法
基于上诉两点,本人查找过不少资料,发现没有找到符合的解决方案,也可能是关键词条不准确的问题,导致很长一段时间陷入泥潭,后来通过编程式选择服务进行远程调用上,找到了修改的思路。
二、常规的微服务设置路由算法方式
通过网上查找资料,发现有以下两种方式进行配置:配置文件、@RibbonClient或@RibbonClients
为了方便后续这两种方式的使用案例,以下先提供一个简单的IP Hash路由算法,根据ip去计算索引:
public class MyRule extends AbstractLoadBalancerRule {@Overridepublic void initWithNiwsConfig(IClientConfig clientConfig) {}@Overridepublic Server choose(Object key) {return this.choose(this.getLoadBalancer(), key);}public Server choose(ILoadBalancer lb, Object key) {if (lb == null) {return null;}List<Server> reachableServers = lb.getReachableServers();// 排序一次,避免从新拉取的服务顺序不一致reachableServers = reachableServers.stream().sorted(Comparator.comparing(Server::getId)).collect(Collectors.toList());HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String remoteAddr = request.getRemoteAddr();int hashCode = Math.abs(remoteAddr.hashCode());int index = hashCode % reachableServers.size();return reachableServers.get(index);}
}
1、配置文件方式
需要在配置文件中加入以下配置
# 你的服务名
your-service-name:ribbon:NFLoadBalancerRuleClassName: com.xxx.yyy.MyRule
通过配置文件配置的方式,发现,启动网关项目之后,通过网关访问该服务的接口,自定的路由策略能够生效。
但是,当后续远程配置文件发生更新之后,也就是,当我们在配置文件中给其他服务也添加上自定义路由算法时(上诉配置复制一份,修改服务名即可),然后执行refresh或bus-refresh通知网关服务进行配置刷新,发现,新增上去的服务还是默认使用轮询算法,也就意味着无法动态为微服务修改路由算法,需要想要生效,就得重启网关服务。
目前经过我自己测试,发现是无法动态修改算法的,也可能是我操作有误之类的。
2、@RibbonClient或@RibbonClients方式
这种方式就不可能实现动态算法修改了,但以下还是简单介绍下这种方式的使用。
1)需要准备一个配置类如下,有一点非常重要,这个配置类不能在标注了@SpringBootApplication注解的启动类扫描路径下,否则,该算法将被全局共享,而达不到微服务定制化。
@Configuration
public class MyRuleConfig {@Beanpublic IRule myRule() {return new MyRule();}}
2)可以在启动器类上添加如下注解
@RibbonClient(name = "your-servcie-name", configuration = {MyRuleConfig.class})
或
@RibbonClients(value = {@RibbonClient(name = "your-servcie-name", configuration = {MyRuleConfig.class})
})
重启网关服务后,通过网关访问该服务,就可以发现使用了自定义的路由算法。
三、通过不懈努力,找到解决思路
温馨提示:这一节涉及到了源码,如果有不想看的小伙伴,直接跳到第四节即可。
1、上述两种方式都不能实现动态配置微服务的路由算法,我查找了很多资料,也没有找到有相对应的解决方案,长时间陷入泥潭。
于是,我换了个思路,能不能通过编程式的方式去修改微服务的路由算法,就是我不通过配置文件,也不通过注解,就通过某个类或类对象,去修改微服务的路由算法,抱着试一试的心态,就去查找到了手动选择微服务实例的实例代码,如下:
@Resource
LoadBalancerClient loadBalancerClient;ServiceInstance serviceInstance = loadBalancerClient.choose("USERINFO-SERVICE");
2、简单看看ServiceInstance
public interface ServiceInstance {/*** @return The unique instance ID as registered.*/default String getInstanceId() {return null;}/*** @return The service ID as registered.*/String getServiceId();/*** @return The hostname of the registered service instance.*/String getHost();/*** @return The port of the registered service instance.*/int getPort();/*** @return Whether the port of the registered service instance uses HTTPS.*/boolean isSecure();/*** @return The service URI address.*/URI getUri();/*** @return The key / value pair metadata associated with the service instance.*/Map<String, String> getMetadata();/*** @return The scheme of the service instance.*/default String getScheme() {return null;}
}
发现ServiceInstance实例对象其实是一个具体待调用服务相关信息,也就意味着,在调用choose这个方法时,已经是通过路由算法选择出了一个服务来的,那么重点就是LoadBalancerClient的choose方法,RibbonLoadBalancerClient实现了LoadBalancerClient,我们只需要看这个类即可。
3、查看RibbonLoadBalancerClient的choose方法,如下
@Override
public ServiceInstance choose(String serviceId) {return choose(serviceId, null);
}public ServiceInstance choose(String serviceId, Object hint) {// 此处已经拿到了具体的服务信息,那么getLoadBalancer就是关键Server server = getServer(getLoadBalancer(serviceId), hint);if (server == null) {return null;}return new RibbonServer(serviceId, server, isSecure(server, serviceId),serverIntrospector(serviceId).getMetadata(server));
}protected ILoadBalancer getLoadBalancer(String serviceId) {return this.clientFactory.getLoadBalancer(serviceId);
}protected Server getServer(ILoadBalancer loadBalancer, Object hint) {if (loadBalancer == null) {return null;}// Use 'default' on a null hint, or just pass it on?return loadBalancer.chooseServer(hint != null ? hint : "default");
}
通过choose方法的层级调用可以知道,选择出一个具体服务需要由loadBalancer.chooseServer获取到。
4、点击chooseServer的实现,可以看到有以三个类可以选择
BaseLoadBalancer
ZoneAwareLoadBalancer
NoOpLoadBalancer
那么到底选谁呢?遇事不决,最简单的就是给三个类chooseServer中都打个断点,然后发起一次接口调用,看看会进入哪个类,不出意外的话,就是ZoneAwareLoadBalancer的chooseServer方法,然而,BaseLoadBalancer又是ZoneAwareLoadBalancer的父类,所以,绕了一下,我们看BaseLoadBalancer的chooseServer方法就完事了。
5、查看BaseLoadBalancer的chooseServer方法,如下:
public Server chooseServer(Object key) {if (counter == null) {counter = createCounter();}counter.increment();if (rule == null) {return null;} else {try {// 此处直接调用了自己的属性对象rulereturn rule.choose(key);} catch (Exception e) {logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);return null;}}
}
可以看到关键的调用rule.choose(key)就拿到了待调用的服务信息,那么rule从那里来?不急,我们往上翻,找到这个属性。
6、在BaseLoadBalancer类的开头可以找到rule这个属性对象
private final static IRule DEFAULT_RULE = new RoundRobinRule();protected IRule rule = DEFAULT_RULE;
看到这,我们也有意识到了,默认情况下,它就是默认创建轮询算法的
接着,我们简单看看这个类的无参构造函数,如下:
public BaseLoadBalancer() {this.name = DEFAULT_NAME;this.ping = null;setRule(DEFAULT_RULE);setupPingTask();lbStats = new LoadBalancerStats(DEFAULT_NAME);}
看到了设置setRule,在点进去看看
public void setRule(IRule rule) {if (rule != null) {this.rule = rule;} else {/* default rule */this.rule = new RoundRobinRule();}if (this.rule.getLoadBalancer() != this) {this.rule.setLoadBalancer(this);}}
重要的来了,敲黑板!
在setRule中, 我们可以看到没在给loadBalancer设置完rule之后,还得把它自己设置给rule,这在第二节中,我们自定义路由算法时,将this.loadBalancer传递到了我们自定义的choose方法中用到了。
看到了这一步,我们就要有这么一个大致流程意识了,就是:
1、在第一步通过loadBalancerClient.choose拿到loadBalancer
2、再通过loadBalancer.chooseServer找到实现类BaseLoadBalancer的chooseServer
3、通过BaseLoadBalancer的chooseServer调用到自己的属性对象的rule.choose拿到具体待调用的服务信息
所以,通过上诉流程,我们简单得出一个结论,就是,我们可以通过获得微服务对应loadBalancer,然后修改它的rule,从而实现动态更改路由算法。但是,我们又如何拿到服务对应的loadBalancer呢,我们目前流程都没有提到过怎么去拿,是因为上面所有流程只是说明了最终是调用loadBalancer中rule的choose方法,接下来我们就讲如何拿到微服务对应的loadBalancer。
7、回到第三步,我们看getLoadBalancer这个方法,如下
protected ILoadBalancer getLoadBalancer(String serviceId) {return this.clientFactory.getLoadBalancer(serviceId);
}
我们发现,它是通过this.clientFactory获取到的loadBalancer,而this.clientFactory的类型是SpringClientFactory。
至此,我就在想,我能不能也通过SpringClientFactory去获取服务的loadBalancer,抱着试一试的态度,我将SpringClientFactory注入进自己的类中,最后发现,真能用,如下:
@Resource
private SpringClientFactory springClientFactory;
我们也能注入使用SpringClientFactory,但它是ribbon包下的一个类,而不是spring默认就有提供的,原因就是在spring-cloud-netflix-ribbon包下有一个类RibbonAutoConfiguration把这个类注册成了Bean,包括像我们第一步所使用LoadBalancerClient,所以我们也能用到这个Bean
@Bean
public SpringClientFactory springClientFactory() {SpringClientFactory factory = new SpringClientFactory();factory.setConfigurations(this.configurations);return factory;
}@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory());
}
好了,自此我们就能通过SpringClientFactory获取到服务对应的loadBalancer,继而改掉loadBalancer的rule,从而实现动态修改rule,接下来就是实践了。
四、验证
1、ServicesRule,用于映射配置文件的结构
public class ServicesRule {private String strategy;private Set<String> services;public String getStrategy() {return strategy;}public void setStrategy(String strategy) {this.strategy = strategy;}public Set<String> getServices() {return services;}public void setServices(Set<String> services) {this.services = services;}
}
2、 RuleProperties,用于与配置文件做关联映射,此处使用@ConfigurationProperties的原因是:当远程配置文件发生变动,执行了refresh或bus-fresh操作时,被该注解修饰的Bean会重新装配,能够重新执行Bean创建的生命周期(不包括重新创建一个新对象),以该类为例,就是每次刷新时,就会执行afterPropertiesSet这个方法。
@ConfigurationProperties(prefix = "services.ribbon")
public class RuleProperties implements InitializingBean, ApplicationContextAware {private List<ServicesRule> rules;private ApplicationContext applicationContext;@Resourceprivate SpringClientFactory springClientFactory;@Overridepublic void afterPropertiesSet() throws Exception {if(rules == null || rules.isEmpty()) {return;}for(ServicesRule rule : rules) {if(StringUtils.isEmpty(rule.getStrategy()) || rule.getServices().isEmpty()) {continue;}Class clazz = Class.forName(rule.getStrategy());if(clazz == null) {continue;}if(!IRule.class.isAssignableFrom(clazz)) {continue;}for(String service : rule.getServices()) {BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) springClientFactory.getLoadBalancer(service);if(baseLoadBalancer == null) {return;}IRule preRule = baseLoadBalancer.getRule();if(preRule.getClass().equals(clazz)) {continue;}AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();IRule iRule = (IRule) beanFactory.createBean(clazz);iRule.setLoadBalancer(baseLoadBalancer);baseLoadBalancer.setRule(iRule);}}}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public List<ServicesRule> getRules() {return rules;}public void setRules(List<ServicesRule> rules) {this.rules = rules;}
}
3、 @ConfigurationProperties需要@EnableConfigurationProperties支持
@EnableConfigurationProperties(value = {RuleProperties.class})
@Configuration
public class RulePropertiesConfiguration {
}
4、配置文件添加以下配置:
# 自动义的可动态更新路由算法的配置
services:ribbon:rules:- strategy: com.xxx.yyy.myRuleservices:- your-service-name
5、测试
能够正常为服务动态添加自定义的路由算法,无须重启网关服务
6、缺点
1、不支持懒加载,通过我们上述操作,提前将服务对应loadBalancer加载出来了。
2、在替换rule的这个过程中可能存在并发问题,如果不介意在切换rule过程中,可能存在一瞬间用户调用存在问题的情况的话,就可以不做处理。否则,需要自己选择更为稳妥的方式进行动态路由实现。
五、总结
通过这么长度篇幅讲了如何动态修改微服务的rule,主要是做一次记录,给大家提供思路,也给我后期遇到同类问题做参考。
相关文章:
网关层针对各微服务动态修改Ribbon路由策略
目录 一、介绍 二、常规的微服务设置路由算法方式 三、通过不懈努力,找到解决思路 四、验证 五、总结 一、介绍 最近,遇到这么一个需求: 1、需要在网关层(目前使用zuul)为某一个服务指定自定义算法IP Hash路由策…...
如何从零开始拆解uni-app开发的vue项目(二)
昨天书写了一篇如何从零开始uni-app开发的vue项目,今天准备写一篇处理界面元素动态加载的案例: 背景:有不同类别的设备,每个设备有每日检查项目、每周检查项目、每年检查项目,需要维保人员,根据不同设备和检查类别对检查项目进行处理,提交数据。 首先看一下界面: &l…...

【生成对抗网络GAN】一篇文章讲透~
目录 引言 一、生成对抗网络的基本原理 1 初始化生成器和判别器 2 训练判别器 3 训练生成器 4 交替训练 5 评估和调整 二、生成对抗网络的应用领域 1 图像生成与编辑 2 语音合成与音频处理 3 文本生成与对话系统 4 数据增强与隐私保护 三、代码事例 四、生成对抗…...

【设计模式】Java 设计模式之模板命令模式(Command)
命令模式(Command)的深入分析与实战解读 一、概述 命令模式是一种将请求封装为对象从而使你可用不同的请求把客户端与接受请求的对象解耦的模式。在命令模式中,命令对象使得发送者与接收者之间解耦,发送者通过命令对象来执行请求…...
如何在Flutter中实现一键登录
获取到当前手机使用的手机卡号,直接使用这个号码进行注册、登录,这就是一键登录。 可以借助极光官方的极光认证实现 1、注册账户成为开发者 2、创建应用开通极光认证 (注意开通极光认证要通过实名审核) 3、创建应用获取appkey、 …...

Amazon SageMaker + Stable Diffusion 搭建文本生成图像模型
如果我们的计算机视觉系统要真正理解视觉世界,它们不仅必须能够识别图像,而且必须能够生成图像。文本到图像的 AI 模型仅根据简单的文字输入就可以生成图像。 近两年,以ChatGPT为代表的AIGC技术崭露头角,逐渐从学术研究的象牙塔迈…...
FPGA数字信号处理前沿
生活在这个色彩斑斓的世界里,大家的身边存在太多模拟信号比如光能、电压、电流、压力、声音、流速等。数字信号处理作为嵌入式研发的一个经久不衰热门话题,可以说大到军工武器、航空航天,小到日常仪器、工业控制,嵌入式SOC芯片数字…...

【Android】系统启动流程分析 —— init 进程启动过程
本文基于 Android 14.0.0_r2 的系统启动流程分析。 一、概述 init 进程属于一个守护进程,准确的说,它是 Linux 系统中用户控制的第一个进程,它的进程号为 1,它的生命周期贯穿整个 Linux 内核运行的始终。Android 中所有其它的进程…...

抖音视频批量下载软件可导出视频分享链接|手机网页视频提取|视频爬虫采集工具
解锁抖音视频无水印批量下载新姿势! 在快节奏的生活中,抖音作为时下最热门的短视频平台之一,吸引着广大用户的目光。而如何高效地获取喜欢的视频内容成为了许多人关注的焦点。Q:290615413现在,我们推出的抖音视频批量下载软件&…...

鸿蒙Harmony应用开发—ArkTS-@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二…...

深度解析:Elasticsearch写入请求处理流程
版本 Elasticsearch 8.x 原文链接:https://mp.weixin.qq.com/s/hZ_ZOLFUoRuWyqp47hqCgQ 今天来看下 Elasticsearch 中的写入流程。 不想看过程可以直接跳转文章末尾查看总结部分。最后附上个人理解的一个图。 从我们发出写入请求,到 Elasticsearch 接收请…...

数据结构:堆和二叉树遍历
堆的特征 1.堆是一个完全二叉树 2.堆分为大堆和小堆。大堆:左右节点都小于根节点 小堆:左右节点都大于根节点 堆的应用:堆排序,topk问题 堆排序 堆排序的思路: 1.升序排序,建小堆。堆顶就是这个堆最小…...

[Halcon学习笔记]在Qt上实现Halcon窗口的字体设置颜色设置等功能
1、 Halcon字体大小设置在Qt上的实现 在之前介绍过Halcon窗口显示文字字体的尺寸和样式,具体详细介绍可回看 (一)Halcon窗口界面上显示文字的字体尺寸、样式修改 当时介绍的设定方法 //Win下QString Font_win "-Arial-10-*-1-*-*-1-&q…...
ArcGis 地图文档
ArcGis官网 https://developers.arcgis.com/labs/android/create-a-starter-app/ Arcgis for android 加载谷歌、高德和天地图 https://blog.csdn.net/qq_19688207/article/details/108125778 AeroMap图层地址: API_KEY: 7e95eae2-a18d-34ce-beaa-894d6a08c5a5 街道图…...

【C语言】动态内存分配
1、为什么要有动态内存分配 不管是C还是C中都会大量的使用,使用C/C实现数据结构的时候,也会使用动态内存管理。 我们已经掌握的内存开辟方式有: int val 20; //在栈空间上开辟四个字节 char arr[10] { 0 }; //在栈空间…...

算法思想总结:位运算
创作不易,感谢三连支持!! 一、常见的位运算总结 标题 二、位1的个数 . - 力扣(LeetCode) 利用第七条特性:n&(n-1)干掉最后一个1,然后每次都用count去统计ÿ…...

四、HarmonyOS应用开发-ArkTS开发语言介绍
目录 1、TypeScript快速入门 1.1、编程语言介绍 1.2、基础类型 1.3、条件语句 1.4、函数 1.5、类 1.6、模块 1.7、迭代器 2、ArkTs 基础(浅析ArkTS的起源和演进) 2.1、引言 2.2、JS 2.3、TS 2.4、ArkTS 2.5、下一步演进 3、ArkTs 开发实践…...

3 Spring之DI详解
5,DI相关内容 前面我们已经完成了bean相关操作的讲解,接下来就进入第二个大的模块DI依赖注入,首先来介绍下Spring中有哪些注入方式? 我们先来思考 向一个类中传递数据的方式有几种? 普通方法(set方法)构造方法 依赖注入描述了在容器中建…...

Web框架开发-Ajax
一、 Ajax准备知识:json 1、json(Javascript Obiect Notation,JS对象标记)是一种轻量级的数据交换格式 1 2 它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。 简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。…...
Python爬虫之urllib库
1、urllib库的介绍 可以实现HTTP请求,我们要做的就是指定请求的URL、请求头、请求体等信息 urllib库包含如下四个模块 request:基本的HTTP请求模块,可以模拟请求的发送。error:异常处理模块。parse:工具模块&#x…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...