自定义Ribbon负载均衡
一. 按照权重实现负载均衡
ribbon本身是没有权重的概念的, 那么如何才能实现代用权重的负载均衡呢?
我们在nacos中, 服务其的集群有一个权重的概念, 当给服务器设置了权重, 那么流量就可以根据权重比例分配到服务器上.
1. 先来看看如何自定义一个负载均衡策略.
首先是继承自AbstractLoadBalancerRule. 从下面这张图可以看出, ribbon自定义的策略, 最终都继承自这个类, 这个类封装了负载均衡策略的公共方法.
在nacos中可以配置服务器的权重
在nacos中,有两个重要的类, 一个是NameService, 一个是ConfigService
- NameService: 注册中心
- ConfigService: 配置中心
二. 实现带有权重的负载均衡器
第一步: 自定义一个带有权重的负载均衡器MyWeightRule
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; /** * 自定义一个权重负载均衡策略 */ @Slf4j public class MyWeightRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { // 读取配置文件, 并且初始化, ribbon内部基本上用不上 } /** * 这个方法是实现负载均衡策略的方法 * * @param * @return */ @Override public Server choose(Object key) { try { log.info("key:{}", key); // 调用父类方法, 获取当前使用的负载均衡器 BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 获取当前服务的名称 String serviceName = baseLoadBalancer.getName(); /** * namingService: 获取nacos的服务发现API */ NamingService namingService = discoveryProperties.namingServiceInstance(); /** * 根据名称获取服务发现实例 * 在selectOneHealthyInstance中, nacos实现了权重的负载均衡算法 */ Instance instance = namingService.selectOneHealthyInstance(serviceName); return new NacosServer(instance); } catch (NacosException e) { e.printStackTrace(); } return null; } }
第二步: 启用自定义的负载均衡器应用
修改自定义的ribbon config.
package com.lxl.www.gateway.config; import com.lxl.www.ribbonconfig.GlobalRibbonConfig; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.context.annotation.Configuration; @Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
设置为全局配置GlobalRibbonConfig.class
然后在全局配置中,我们执行当前使用的负载均衡策略是自定义的权重负载均衡策略
package com.lxl.www.ribbonconfig; import com.lxl.www.gateway.myrule.MyWeightRule; import com.netflix.loadbalancer.IRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 实现带有权重的负载均衡策略 return new MyWeightRule(); } }
第三步:启动服务, 并设置nacos权重
我们看到启动了两台product, 一台order. 接下来设置product两台实例的权重
我们看到一个设置为0.1, 另一个是0.9, 也就是说如果有10次请求, 基本上都是会打到8083端口上的.
第四步: 浏览器访问连接, 测试结果
http://localhost:8080/get/product
触发了10次请求, 基本上都打到了8083服务器上.
三 实现同集群优先调用原则的负载均衡器
尽量避免跨集群调用
比如, 南京集群的product优先调用南京集群的order . 北京集群的product优先调用北京集群的order.
实现如上图所示的功能
我们有product服务和order服务, 假设各有10台. product和order有5台部署在北京集群上, 另外5台部署在南京集群上.
那么当有请求打到南京的product的时候, 那么他调用order要优先调用南京集群的, 南京集群没有了, 在调用北京集群的.
当有请求打到北京的product的是偶, 优先调用北京集群的order, 北京没有找到, 再去调用南京的order.
第一步: 自定义一个同集群优先策略的负载均衡器
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.omg.PortableInterceptor.INACTIVE; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * 同集群优先调用--负载均衡策略 */ @Slf4j public class TheSameClusterPriorityRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * 主要实现choose方法 * * @param o * @return */ @Override public Server choose(Object o) { try { // 第一步: 获取服务所在的集群名称 String clusterName = discoveryProperties.getClusterName(); // 第二步: 获取当前负载均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 第三步: 获取当前服务的实例名称 String serviceName = loadBalancer.getName(); // 第四步: 获取nacos client服务注册发现api NamingService namingService = discoveryProperties.namingServiceInstance(); // 第五步: 通过namingService获取当前注册的所有服务实例 List<Instance> allInstances = namingService.getAllInstances(serviceName); List<Instance> instanceList = new ArrayList<>(); // 第六步: 过滤筛选同集群下的服务实例 for (Instance instance: allInstances) { if (StringUtils.endsWithIgnoreCase(instance.getClusterName(), clusterName)) { instanceList.add(instance); } } Instance toBeChooseInstance; // 第七步: 选择合适的服务实例 if (instanceList == null || instanceList.size() == 0) { // 从其他集群中随机选择一个服务实例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstances); log.info("跨集群调用---{}", toBeChooseInstance.getPort()); } else { // 从本集群中随机选择一个服务实例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(instanceList); log.info("同集群调用---{}", toBeChooseInstance.getPort()); } return new NacosServer(toBeChooseInstance); } catch (NacosException e) { e.printStackTrace(); } return null; } }
package com.lxl.www.gateway.myrule; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.client.naming.core.Balancer; import java.util.List; /** * 根据权重随机选择一个实例 */ public class WeightedBalancer extends Balancer { public static Instance chooseInstanceByRandomWeight (List<Instance> instanceList) { return getHostByRandomWeight(instanceList); } }
第二步: 设置当前的负载均衡策略为同集群优先策略
@Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
@Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 实现带有权重的负载均衡策略 //return new MyWeightRule(); // 实现同集群优先的服务实例 return new TheSameClusterPriorityRule(); } }
第三步: 启动服务
可以看到order服务实例有1台, 所属集群是BJ-CLUSTER, product服务有4台. BJ-CLUSTER和NJ-CLUSTER各两台
第四步: 访问请求
http://localhost:8080/get/product
由于order服务是BJ-CLUSTER, 所以, 我们看到流量是随机打到了ORDER的BJ-CLUSTER集群上.
停止BJ-CLUSTER集群的两台实例, 现在BJ-CLUSTER集群上没有order的服务实例了, 这是在请求, 流量就会达到NJ-CLUSTER上.
四. 金丝雀发布--实现同版本同集群优先负载均衡策略
金丝雀发布, 也称为灰度发布, 是什么意思呢?
首先, 我们的product服务实例有100台, order服务实例有100台. 现在都是在v1 版本上
然后新开发了一个变化很大的功能, 为了保证效果, 要进行灰度测试
在product-center上发布了5台, 在order-center上发布了5台
那么现在用户的流量过来了, 我们要设定一部分用户, 请求的是v1版本的流量, 而且全部都走v1版本, product-center, order-center都是v1版本
如果过来的用户, 请求的v2版本的流量, 那么product和order都走v2版本.
下面我们要实现的功能描述如下:
1. 同集群,同版本优先调用,
2. 没有同集群的服务提供者, 进行跨集群,同版本调用
3. 不可以进行不同版本间的调用
也就是说: 南京集群V1版本的product优先调用南京集群V1版本的order, 如果没有南京集群V1版本的order, 那么就调用北京集群V1版本的order, 那么能调用v2版本的么? 当然不行.
下面来具体实现一下
第一步: 实现带有版本的集群优先策略的负载均衡算法
package com.lxl.www.gateway.myrule; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.List; /** * 同集群优先带版本的负载均衡策略 */ @Slf4j public class TheSameClusterPriorityWithVersionRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { try { // 第一步: 获取当前服务的集群名称 和 服务的版本号 String clusterName = discoveryProperties.getClusterName(); String version = discoveryProperties.getMetadata().get("version"); // 第二步: 获取当前服务的负载均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); // 第三步: 获取目标服务的服务名 String serviceName = loadBalancer.getName(); // 第四步: 获取nacos提供的服务注册api NamingService namingService = discoveryProperties.namingServiceInstance(); // 第五步: 获取所有服务名为serviceName的服务实例 List<Instance> allInstances = namingService.getAllInstances(serviceName); // 第六步: 过滤相同版本的服务实例 List<Instance> sameVersionInstance = new ArrayList<>(); for (Instance instance : allInstances) { if (instance.getMetadata().get("version").equals(version)) { sameVersionInstance.add(instance); } } // 第七步: 过滤有相同集群的服务实例 List<Instance> sameClusterInstance = new ArrayList<>(); for (Instance instance: sameVersionInstance) { if (instance.getClusterName().equals(clusterName)) { sameClusterInstance.add(instance); } } // 第八步: 选择合适的服务实例 Instance toBeChooseInstanc; if (sameClusterInstance == null || sameClusterInstance.size() == 0) { toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameVersionInstance); log.info("同版本不同集群的服务实例--{}", toBeChooseInstanc.getPort()); } else { toBeChooseInstanc = WeightedBalancer.chooseInstanceByRandomWeight(sameClusterInstance); log.info("同版本同集群服务实例--{}", toBeChooseInstanc.getPort()); } return new NacosServer(toBeChooseInstanc); } catch (NacosException e) { e.printStackTrace(); } return null; } }
第二步: 启用自定义负载均衡策略
@Configuration /*@RibbonClients(value = { @RibbonClient(name="product", configuration = ProductConfiguration.class), @RibbonClient(name = "customer", configuration = CustomerConfiguration.class) })*/ // 使用全局的配置 @RibbonClients(defaultConfiguration = GlobalRibbonConfig.class) public class CustomeRibbonConfig { }
@Configuration public class GlobalRibbonConfig { @Bean public IRule getRule() { // 实现带有权重的负载均衡策略 //return new MyWeightRule(); // 实现同集群优先的负载均衡策略 // return new TheSameClusterPriorityRule(); // 实现同版本集群优先的服务负载均衡策略 return new TheSameClusterPriorityWithVersionRule(); } }
第三步:启动服务
如图, 我们启动了两个服务, 一个order, 一个product, 其中order启动了1个实例, product启动了4个实例.
下面来看看order实例的详情
属于北京集群, 版本号是v1版本
再来看看product集群的详情
北京集群有两台实例, 一个版本号是v1,另一个是v2
南京集群有两台实例, 一个是v1, 另一个也是v2
第四步: 现在发起请求
http://localhost:8080/get/product
order集群是北京, 版本是version1. 那么服务应该达到哪台服务器呢? 应该打到北京集群的v1版本服务,端口号是8081的服务器.
果然,请求达到了8081服务上,
加入这时候, 北京集群的8081宕机了, 流量应该达到南京集群的v1版本的集群上, 端口号是8083
果然是8083.
那么如果8083也宕机了, 流量会打到v2服务器上么?
没错,不会的, 报错了, 没有服务