网关层针对各微服务动态修改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,主要是做一次记录,给大家提供思路,也给我后期遇到同类问题做参考。

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

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

相关文章

JUC并发编程(五)

1、java内存模型 Java内存模型&#xff08;Java Memory Model&#xff0c;JMM&#xff09;是Java编程语言中用于处理并发编程的一组规则和规范。它定义了Java程序中多线程之间如何交互以及内存如何被共享和访问的规则。它定义了主内存&#xff0c;工作内存的抽象概念&#xff0…

一文让你简单了解跨境电商需要购买堡垒机的几大原因

随着互联网技术的快速发展&#xff0c;跨境电商蓬勃发展&#xff0c;但发展过程中网络安全问题日益凸显。因此不少跨境电商企业购买了堡垒机。这是为什么呢&#xff1f;一文让你简单了解跨境电商需要购买堡垒机的几大原因。 一文让你简单了解跨境电商需要购买堡垒机的几大原因 …

代码随想录day26(2)二叉树:二叉搜索树中的众数(leetcode501)

题目要求&#xff1a;给定一个有相同值的二叉搜索树&#xff08;BST&#xff09;&#xff0c;找出 BST 中的所有众数。结点左子树中所含结点的值小于等于当前结点的值&#xff0c;结点右子树中所含结点的值大于等于当前结点的值。 思路&#xff1a;如果不考虑二叉搜索树&#…

TCP机械臂控制

通过w(红色臂角度增大)s&#xff08;红色臂角度减小&#xff09;d&#xff08;蓝色臂角度增大&#xff09;a&#xff08;蓝色臂角度减小&#xff09;按键控制机械臂 注意&#xff1a;关闭计算机的杀毒软件&#xff0c;电脑管家&#xff0c;防火墙 1&#xff09;基于TCP服务器…

最近接到一个大项目,给公司设计抢商品代金劵业务

我们公司是做汽车金融方面的工作&#xff0c;在业内还挺大。目前单量来源于2&#xff0c;3线城市&#xff0c;随着大环境越老越差位了吸引他们&#xff0c; 公司决定给全国的客户&#xff0c;销售等发一些商品 1.总体学习了京东开源秒杀系统设计思路和方案。 我们公司决定进行…

博途PLC 高速计数器复位功能块(HC_Reset)

高速计数器的使用和编码器应用请参考下面文章链接: 1、普通开关计米功能块(博途高速计数器应用) https://rxxw-control.blog.csdn.net/article/details/132354435https://rxxw-control.blog.csdn.net/article/details/1323544352、S7-1200PLC编码器转速测量功能块(高速计数…

c++算法学习笔记 (15) 质数

1.试除法判断某个数是否为质数 #include <iostream> using namespace std; const int N 50005; bool is_prime1(int n) { // 暴力写法&#xff1a;O(n)if (n < 2)return false;for (int i 2; i < n; i){if (n % i 0)return false;}return true; } // 优化&…

含“AI”量上涨,智能模组SC208系列助力智慧零售全场景高质发展

AI正重塑智慧零售产业&#xff0c;加速零售在采购、生产、供应链、销售、服务等方面改善运营效率和用户体验。零售行业经历了从线下到线上再到全渠道融合发展过程&#xff0c;“提质、降本、增效、高体验”是亘古不变的商业化与智能化方向。含“AI”量逐渐上涨的智慧零售正经历…

Git——IDEA中的使用详解

目录 Git1、IDEA中配置Git2、将本地项目推送到远程仓库2.1、创建项目远程仓库2.2、初始化本地仓库2.3、连接远程仓库2.4、提交到本地仓库2.5、推送到远程仓库 3、克隆远程仓库到本地4、基本操作4.1、代码提交到暂存区4.2、暂存区代码提交到本地库4.3、推送到远程仓库4.4、撤销本…

VMware的二次开发

VMware的Virtual Disk format https://www.vmware.com/app/vmdk/?srcvmdk 在这个文档中描述了VMDK的格式。 当前VMware提供了多种开发库&#xff1a; vsphere soap api: provide the interface to manage vshpere. Virtual Disk Development Kit&#xff1a;VMware Virtu…

HJ3 明明的随机数

HJ3 明明的随机数 题目描述 明明生成了&#xfffd;N个1到500之间的随机整数。请你删去其中重复的数字&#xff0c;即相同的数字只保留一个&#xff0c;把其余相同的数去掉&#xff0c;然后再把这些数从小到大排序&#xff0c;按照排好的顺序输出。 数据范围&#xff1a; 1≤…

nginx反代后java的request.getScheme获取不到https的解决办法

在实际工作中,经常会用到nginx反向代理应用,访问https页面Java 通过request.getScheme()获取不到https协议,或者response.sendRedirect重定向是http,而不是我们想要的https。 问题原因 经过反代后,协议信息没有转发到后端,或者后端没有设置 protocolHeader nginx配置 …

leetCode刷题 18. 四数之和

目录 注意&#xff1a;正常提交后有问题。 1. 思路 2. 解题方法 2.1 排序数组 2.2 双指针遍历 3. 复杂度 4. Code 题目&#xff1a; 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], …

大数据面试总结 四

1、当hadoop集群中某一个节点挂了&#xff0c;内部数据流程是如何进行的&#xff1f; 每一个datanode都会定期向namenode发送heardbeat消息&#xff0c;当一段时间namenode没有接收到某一个datanode的消息&#xff0c;此时namenode就会将该datanode标记为死亡&#xff0c;并不…

TouchGFX之性能测量

TouchGFX Core开放了几个信号&#xff0c;可用于测量性能。 当这些信号在内部触发时&#xff0c;用户可在应用程序中同步触发单个GPIO&#xff0c;从而实现“渲染时间”和其他有用信号的可视化。 信号在GPIO.hpp中定义 /* 用于操作GPIO的接口类&#xff0c;以便在目标硬件上进…

力扣---完全平方数

思路&#xff1a; 还是比较好想的&#xff0c;g[i]定义为和为 i 的完全平方数的最少数量。那么递推关系式是g[i]min(g[i-1],g[i-4],g[i-9],...)1&#xff0c;数组初始化是g[0]0,g[1]1。注意这里要对g[0]初始化&#xff0c;&#xff08;举个例子&#xff09;因为在遍历到g[4]时&…

数据库迁移测试

数据库 数据源变更&#xff1a;数据库名称、数据库类型等&#xff0c;一般情况下在同类型的数据库之间迁移比较常见&#xff0c;但是也存在不同类型的数据库之间的迁移&#xff0c;例如&#xff1a;oracle到MySQL等等&#xff0c;非关系型数据库到关系型&#xff0c;关系型到非…

14 网络管理与网络安全(1)

1.网络管理 在网络管理中&#xff0c;一般采用网络管理者-网管代理模型。管理者实质上是运行在计算机操作系统之上的一组应用程序&#xff0c;代理位于被管理的设备内部。一个管理者可以和多个代理之间进行信息交换。网络管理一般采用集中式网络管理或者分布式网络管理。集中式…

基于Springboot+Vue3的大学生毕业作业设计之—招投标分析系统

有系统开发需求可私信我提供帮助哦 ~ 部分 import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.L…

七、Java中SpringBoot组件集成接入【Minio文件服务器】

七、Java中SpringBoot组件集成接入【Minio文件服务器】 1.Minio介绍2.搭建Minio服务2.1Windows部署2.2Linux部署2.3docker部署 3.Minio可视化操作4.SpringBoot接入Minio1.添加maven依赖2.yaml配置文件3.配置类4.工具类5.控制类 5.常见问题6.其他参考文章 1.Minio介绍 对象存储…