1.切换ribbon自带的负载均衡算法
- 在上一篇博客中我们测试了ribbon的默认负载均衡算法,就是轮询,但是我们还可以自定义它的负载均衡算法,代替它的默认轮询算法
- 在自己实现策略之前,还是按照原来的套路,先去看看ribbon怎么实现的负载均衡算法,我们在客户端集成ribbon实现负载均衡的时候只是导入了ribbon的依赖,并自定义了一个config类,用于编写一个@Bean方法将RestTemplate对象装配到spring容器中,并在该@Bean方法上使用了注解@LoadBalanced用于开启客户端使用ribbon实现负载均衡
- 所以我们需要去注解@LoadBalanced中查询ribbon实现的负载均衡的算法,但是通过查看注解@LoadBalanced的源码我们可以发现,这个注解中并没有什么特别的地方,所以就去百度了一下,通过百度结果我们可以发现ribbon的负载均衡算法实现都是根据接口IRule实现的,所以我们可以看看这个接口的源码
public interface IRule { Server choose(Object var1); void setLoadBalancer(ILoadBalancer var1); ILoadBalancer getLoadBalancer(); }
- 可以发现这个接口中主要的就是设置负载均衡的算法(setLoadBalancer())和设置选中的server(choose())两个方法,所以我们需要参考的实行类来实现自定义负载均衡算法
- 第一个一般不会使用
- AvailabilityFilteringRule:用于首先过滤掉已经崩溃的服务者提供的服务(或者说已经跳闸的服务),即先过滤掉已经故障的服务,然后再在剩下的服务中进行轮询【所以AvailabilityFilteringRule就是优化版的轮询】
- RoundRobinRule:就是我们说的轮询策略
- RandomRule:这也是我们前面说过的随机策略
- WeightedResponseTimeRule:就是按照权重实现负载均衡
- RetryRule:首先使用轮询获取服务,如果获取的服务跳闸了,就会在指定的时间内进行重连
- 测试默认轮询效果
- 使用ribbon自己提供的随机负载均衡算法,只需要在config类中将RandomRule对象注入spring容器中即可
@Bean public IRule myRule(){ return new RandomRule(); }
- 通过上面的测试效果,我们可以发现只要将不同的ribbon的负载均衡策略的实现类通过config类装配到spring容器中,就可以实现ribbon不同的负载均衡算法的切换,上面的例子中就是要了默认算法轮询和指定算法随机,开启消费者model之后都是刷新3次查看结果
- 所以我们自己在实现了自定义负载均衡算法之后,也需要将实现的算法类通过config类装配到spring容器中去
2.分析如何实现ribbon的自定义负载均衡算法
- 以上就是常用的几种ribbon实现负载均衡的算法,我们可以观察它的的共性,然后照猫画虎实现自定义的负载均衡算法
- 首先上面的这些是实行类都直接或间接的继承了一个IRule接口的实行类AbstractLoadBalancerRule,所以我们自己实现的时候也应该去继承它,除此之外实行类的具体实现还是需要具体的去参考1-2个ribbon的具体实行类,这里我们就参考最经典的轮询算法的实现
- ribbon中轮询算法实现源码
public class RoundRobinRule extends AbstractLoadBalancerRule { private AtomicInteger nextServerCyclicCounter; private static final boolean AVAILABLE_ONLY_SERVERS = true; private static final boolean ALL_SERVERS = false; private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class); public RoundRobinRule() { this.nextServerCyclicCounter = new AtomicInteger(0); } public RoundRobinRule(ILoadBalancer lb) { this(); this.setLoadBalancer(lb); } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { List<Server> reachableServers = lb.getReachableServers(); List<Server> allServers = lb.getAllServers(); int upCount = reachableServers.size(); int serverCount = allServers.size(); if (upCount != 0 && serverCount != 0) { int nextServerIndex = this.incrementAndGetModulo(serverCount); server = (Server)allServers.get(nextServerIndex); if (server == null) { Thread.yield(); } else { if (server.isAlive() && server.isReadyToServe()) { return server; } server = null; } continue; } log.warn("No up servers available from load balancer: " + lb); return null; } if (count >= 10) { log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; } } } private int incrementAndGetModulo(int modulo) { int current; int next; do { current = this.nextServerCyclicCounter.get(); next = (current + 1) % modulo; } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); return next; } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } }
- 我们主要找这个类怎么实现的IRule中的choose方法(即选中哪一个server的算法)
public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { log.warn("no load balancer"); return null; } Server server = null; int count = 0; //初始计数=0 while (server == null && count++ < 10) { //count++ List<Server> reachableServers = lb.getReachableServers();//获取可访问的server/在线的server List<Server> allServers = lb.getAllServers();//获取所有server int upCount = reachableServers.size();//获取list的大小 int serverCount = allServers.size();//获取list的大小 if ((upCount == 0) || (serverCount == 0)) {//如果上面的两个list有一个=0,则不进行轮询 log.warn("No up servers available from load balancer: " + lb); return null; } int nextServerIndex = incrementAndGetModulo(serverCount);//下一个server在list中的下标 server = allServers.get(nextServerIndex);//获取对应的server if (server == null) { //server为空就线程礼让 /* Transient. */ Thread.yield(); continue; } if (server.isAlive() && (server.isReadyToServe())) { //对应的server在线并且可以提供服务,就返回当前的server return (server); } // Next. server = null; } if (count >= 10) { //上面的负载均衡器尝试10次后还没有获得到可用的、活跃的/在线的 server,返回null log.warn("No available alive servers after 10 tries from load balancer: " + lb); } return server; }
- 自定义ribbon负载均衡算法/ribbon客户端
- 注解@RibbonClient中的name属性用于指定注册中心中需要使用我们自定义的ribbon策略实现负载均衡的服务者集群
- 注解@RibbonClient中的configuration属性用于指定我们自定义的实现Ribbon的负载均衡算法的类的class对象
- 注意:注解@RibbonClient中的name属性用于指定注册中心的哪一个服务集群使用我们自定义的负载均衡策略,所以我们就不能直接将我们定义的自定义负载均衡类放在spring boot项目自动扫描的包路径下,即不能放在spring boot项目的入口程序的同级目录的文件夹中,所以我们不能直接将自定义的负载均衡类装配到原来定义好的config类中;
- 按照官方doc的解释,这是因为只要我们将自定义的类放在了spring boot项目的入口程序的同级目录的文件夹中,那么我们自定义的负载均衡策略将对注册中心中所有的服务提供者提供的服务集群生效,而不能实现只在我们指定的服务提供者提供的服务集群上生效,所以我们需要在非spring boot项目的入口程序的同级目录的文件夹中创建一个config类,为这个类加上注解@Configuration和注解@RibbonClient(name = “要应用自定义负载均衡策略的服务集群名称”,configuration = 自定义的负载均衡的类.class),最后在内部写一个@Bean方法将我们自定义的负载均衡类装配到spring容器中
- 我们可以模仿某一个ribbon已经实现了的算法进行自定义负载均衡算法编写,比如ribbon实现的随机算法
package com.netflix.loadbalancer; import com.netflix.client.config.IClientConfig; import java.util.List; import java.util.concurrent.ThreadLocalRandom; public class RandomRule extends AbstractLoadBalancerRule { // @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) {//线程被中断就直接结束方法调用 return null; } List<Server> upList = lb.getReachableServers(); //获取在线的、可以提供服务的服务list List<Server> allList = lb.getAllServers(); //获取所有的服务 int serverCount = allList.size(); //获取所有的服务的个数 if (serverCount == 0) { //如果注册中心中没有服务可以获取就直接结束方法调用 return null; } int index = chooseRandomInt(serverCount); //获取随机数 server = upList.get(index); //随机数作为下标从在线的、可以提供服务的服务list中获取一个Server if (server == null) { //如果获取到的Server对象为null,线程礼让并结束本次循环 Thread.yield(); continue; } if (server.isAlive()) { //如果服务可以被消费 return (server); //返回该服务对象,即选中这一次使用这个服务进行消费 } server = null; //将server变量清空,使得下一次获取的服务为使用上面的步骤重新挑选出来的server对象 Thread.yield(); //线程礼让 } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } }
- 我们的算法目标:每个集群中某一个服务者提供的服务被访问5次,5次之后才换下一个集群中的服务者提供的服务
- 思考:当我们现在集群的3个服务全都被执行了5次之后怎么办?应该设置一个计算器count,默认为0,一旦计数到达5就将其清零并跳转使用下一个可以消费的服务;但是3个服务怎么切换?再使用一个下标指示值index,默认为0,当count>=5的时候index+1,当index>=3的时候,就将index置0
3.代码实现
- 编写Ribbon自定义负载均衡算法类
package com.thhh.myRule; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import org.springframework.context.annotation.Configuration; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @Configuration public class myRandomRule extends AbstractLoadBalancerRule { private int count = 0;//一个服务被调用的次数计数 private int index = 0;//当前提供服务的server的下标 //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { return null; } /* int index = chooseRandomInt(serverCount); server = upList.get(index);*/ //======================================= this.count++; if (this.count>=5){ this.index++; if (this.index>=upList.size()){ this.index = 0; } this.count = 0; } server = upList.get(index); //======================================= if (server == null) { Thread.yield(); continue; } if (server.isAlive()) { return (server); } server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } }
- 编写config类装配自定义的Ribbon负载均衡算法对象到spring容器中
package com.thhh.myRule; import com.netflix.loadbalancer.IRule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 用于将自定义的ribbon负载均衡算法的实现类装配到spring容器中 */ @Configuration public class myRule{ @Bean public IRule myRandomRule(){ return new myRandomRule();//装配自定义的负载均衡算法的实现类装配到spring容器中 } }
- 在入口程序上加上注解@RibbonClient(name = “SPRINGCOULD-PROVIDER-DEPT”,configuration = myRule.class)
package com.thhh.springcould; import com.thhh.myRule.myRule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.ribbon.RibbonClient; @SpringBootApplication @EnableEurekaClient @RibbonClient(name = "SPRINGCOULD-PROVIDER-DEPT",configuration = myRule.class) public class DeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(DeptConsumer_80.class,args); } }
- 测试
通过测试,确实是每一个服务被消费5次之后就切换下一个服务提供者一个的服务,15次刷新之后再次轮转3个服务提供者提供的服务,一共15张图,就不截图了,亲测有效