Ribbon + Nacos 自定义负载均衡算法
参考:https://blog.csdn.net/yuanyuan_gugu/article/details/107336264
一、自定义负载均衡算法
自定义负载均衡算法的实现步骤
(1)RestTemplate 注入增加 @LoadBalanced 注解;
(2)继承 AbstractLoadBalancerRule 类;
(3)重写 choose 方法;
(4)配置文件配置自定义的负载均衡算法;
二、基于Nacos的负载均衡实现
1、基于Nacos权重
(1)注册到 nacos 的服务有权重的定义,可以在配置文件中通过 spring.cloud.nacos.discovery.weight=0.1
定义权重,默认为1;
将Nacos中微服务实例调整权重数值(0-1之间,越大权重越高)。基于权重的负载均衡可以结合微服实例部署的环境,更合理的进行负载均衡,例如部署环境服务器配置较高,可用资源较为宽裕,可调高权重参数,反之可以调低。
(2)我们可以根据权重去作为负载均衡的算法,例如我们同一个服务部署了3个实例,它们的性能都是不一样的,我们想让性能最好的那个优先去被选择;我们可以设置性能最好的设置服务权重设置最大。
权重负载均衡的算法实现,如下:
@Slf4j public class CustomWeightRibbonRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { log.info("------ key: {}", o); // 获取负载均衡的对象 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 获取当前调用的微服务的名称 String serviceName = baseLoadBalancer.getName(); // 获取Nocas服务发现的相关组件API NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); try { // 获取一个基于 nacos client 实现权重的负载均衡算法 Instance instance = namingService.selectOneHealthyInstance(serviceName); // 返回一个nacos的server return new NacosServer(instance); } catch (NacosException e) { log.error("error: ", e); return null; } } }
(3)配置文件,如下:
product-center: ribbon: NFLoadBalancerRuleClassName: com.yufeng.custom.CustomWeightRibbonRule
2、支持Nacos同一集群优先调用
实际项目中,微服务的部署为了确保高可用和容灾性,常常使用异地机房的多套部署。如何保证微服务交互时总是优先使用就近机房的实例?
使用Nacos的集群配置Cluster可以实现同集群优先调用。
(1)nacos 可以通过配置文件配置 spring.cloud.nacos.discovery.cluster-name=NJ-CLUSTER
集群名称;
(2)例如我们有两个服务 order-center,product-center, 在南京机房和北京机房都部署了一套 order-center,product-center;我们在服务调用的时候要优先调用同一个集群的服务。
同集群优先调用负载均衡算法的实现,如下:
@Slf4j public class CustomClusterRibbonRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { log.info("-------key: {}", o); // 获取当前服务所在的集群 String currentClusterName = nacosDiscoveryProperties.getClusterName(); // 获取当前调用的微服务的名称 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); String serviceName = baseLoadBalancer.getName(); // 获取nacos client的服务注册发现组件的api NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); try { // 获取多有的服务实例 List<Instance> allInstances = namingService.getAllInstances(serviceName, true); // 获取同一集群下的所有被调用的服务 List<Instance> sameClusterNameInstList = allInstances.stream() .filter(instance -> StringUtils.equalsIgnoreCase(instance.getClusterName(), currentClusterName)) .collect(Collectors.toList()); Instance chooseInstance; if(sameClusterNameInstList.isEmpty()) { // 根据权重随机选择一个 chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(allInstances); log.info("发生跨集群调用--->当前微服务所在集群:{},被调用微服务所在集群:{},Host:{},Port:{}", currentClusterName, chooseInstance.getClusterName(), chooseInstance.getIp(), chooseInstance.getPort()); } else { chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameClusterNameInstList); log.info("同集群调用--->当前微服务所在集群:{},被调用微服务所在集群:{},Host:{},Port:{}", currentClusterName, chooseInstance.getClusterName(), chooseInstance.getIp(), chooseInstance.getPort()); } return new NacosServer(chooseInstance); } catch (NacosException e) { log.error("error: ", e); return null; } } } class ExtendBalancer extends Balancer { /** * 根据权重选择随机选择一个 */ public static Instance getHostByRandomWeightCopy(List<Instance> hosts) { return getHostByRandomWeight(hosts); } }
此时调用Nacos的基于权重的负载均衡算法时,已经不能再使用 namingService.selectOneHealthyInstance(name),因为我们不是仅仅基于服务名称做全集群的带权重的负载均衡选择实例,而是要基于部分实例列表来进行。
3、基于Nacos元数据的版本控制
实际项目,我们可能还会有这样的需求:
一个微服务在线上可能多版本共存,且多个版本的微服务并不兼容。使用Nacos的自定义元数据,可以实现微服务的版本控制。
配置文件的格式: spring.cloud.nacos.discovery.metadata.{key}={value}
当前配置的版本: spring.cloud.nacos.discovery.metadata.version=V1
必须同版本,优先同集群调用
@Slf4j public class ClusterMetaDataRibbonRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { log.info("-------key: {}", o); // 获取当前服务的集群名称 String currentClusterName = nacosDiscoveryProperties.getClusterName(); // 获取当前版本 String currentVersion = nacosDiscoveryProperties.getMetadata().get("version"); // 获取被调用的服务的名称 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) getLoadBalancer(); String serviceName = baseLoadBalancer.getName(); // 获取nacos clinet的服务注册发现组件的api NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); try { // 获取所有被调用服务 List<Instance> allInstances = namingService.getAllInstances(serviceName); // 过滤出相同版本且相同集群下的所有服务 List<Instance> sameVersionAndClusterInstances = allInstances.stream() .filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion) && StringUtils.equalsIgnoreCase(x.getClusterName(), currentClusterName) ).collect(Collectors.toList()); Instance chooseInstance; if(sameVersionAndClusterInstances.isEmpty()) { // 过滤出所有相同版本的服务 List<Instance> sameVersionInstances = allInstances.stream() .filter(x -> StringUtils.equalsIgnoreCase(x.getMetadata().get("version"), currentVersion)) .collect(Collectors.toList()); if(sameVersionInstances.isEmpty()) { log.info("跨集群调用找不到对应合适的版本当前版本为:currentVersion:{}",currentVersion); throw new RuntimeException("找不到相同版本的微服务实例"); } else { // 随机权重 chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameVersionInstances); log.info("跨集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}", currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("current-version"), chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort()); } } else { chooseInstance = ExtendBalancer.getHostByRandomWeightCopy(sameVersionAndClusterInstances); log.info("同集群同版本调用--->当前微服务所在集群:{},被调用微服务所在集群:{},当前微服务的版本:{},被调用微服务版本:{},Host:{},Port:{}", currentClusterName, chooseInstance.getClusterName(), chooseInstance.getMetadata().get("version"), chooseInstance.getMetadata().get("current-version"), chooseInstance.getIp(), chooseInstance.getPort()); } return new NacosServer(chooseInstance); } catch (NacosException e) { log.error("error,", e); return null; } } }