1.Ribbon默认轮询算法原理
先将注解@RibbonClient
注释掉。让它恢复到最开始的轮询算法。
轮询算法的原理如下。妙不妙?
2.RoundRobinRule源码解读
我们先解读下RoundRobinRule
轮询算法的源码实现,方便后面仿照轮询算法实现默认的负载均衡算法。
先看接口IRule
。
public interface IRule { Server choose(Object var1); void setLoadBalancer(ILoadBalancer var1); ILoadBalancer getLoadBalancer(); }
里面有一个choose
方法,看看在RoundRobinRule
中的具体实现吧。
public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public Server choose(ILoadBalancer lb, Object key) { // 如果没有负载均衡算法,返回null if (lb == null) { log.warn("no load balancer"); return null; } else { Server server = null; int count = 0; while(true) { if (server == null && count++ < 10) { //获取状态为up(活着的)服务器 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; } } }
看看incrementAndGetModulo方法
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; }
3.手写轮询算法
3.1 8001和8002微服务改造
在8001和8002的PaymentController中加上这个方法,用于测试我们的自定义轮询:
@GetMapping("/lb") public String getPaymentLB(){ return serverPort; }
3.2 订单微服务改造
将订单微服务的负载均衡注解去掉
在springcloud包下新建lb.ILoadBalancer接口(自定义负载均衡机制(面向接口))
public interface LoadBalancer { // 传入具体的服务集合,返回服务实例 ServiceInstance instances(List<ServiceInstance> instances); }
在lb包下新建自定义ILoadBalancer接口的实现类,实现负载均衡的核心逻辑。下面用到了CAS自旋锁的知识,让代码很健壮。
@Component public class MyLB implements LoadBalancer { // 新建一个原子整形实例,记录访问次数,使线程安全 private AtomicInteger visitCount = new AtomicInteger(0); public final int getAndIncrement() { int current; int next; do { current = visitCount.get(); //如果current是最大值,重新计算,否则加1(防止越界), // 正常情况肯定不会出现越界的情况,但是我们可以学习源码这种方式,提升代码健壮性 next = current >= Integer.MAX_VALUE ? 0 : current + 1; // 当visitCount与current相等时,说明cas成功将visitCount更新为next,终止循环 // 当visitCount与current不相等时,说明有其他线程操作atomicInteger,返回true,取反为false,循环操作 } while (!this.visitCount.compareAndSet(current, next)); System.out.println("****访问次数:" + next); // 返回的next即visitCount自增成功后的值 return next; } @Override public ServiceInstance instances(List<ServiceInstance> instances) { // 轮询算法 int index = getAndIncrement() % instances.size(); return instances.get(index); } }
接着在我们的OrderController代码逻辑里来引入自己的自旋锁吧。
@Resource private ILoadBalancer iLoadBalancer; @Resource private DiscoveryClient discoveryClient; @GetMapping("/payment/lb") public String getPaymentLB(){ List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); //判断服务有效 if (instances ==null || instances.size() <=0){ return null; } ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); System.out.println(uri); return restTemplate.getForObject(uri+"/payment/lb",String.class); }
3.3 测试
启动Eureka Server集群7001,7002,支付微服务集群8001,8002,订单80微服务。