SpringCloud之手写Ribbon轮询算法

698 阅读2分钟

Ribbon提供的负载均衡策略如下: 在这里插入图片描述 今天看一下默认的轮询策略是如何实现的

RoundRobinRule核心代码如下:

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        }

        Server server = null;
        int count = 0;
        while (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)) {
                log.warn("No up servers available from load balancer: " + lb);
                return null;
            }

            int nextServerIndex = incrementAndGetModulo(serverCount);
            server = allServers.get(nextServerIndex);

            if (server == null) {
                /* Transient. */
                Thread.yield();
                continue;
            }

            if (server.isAlive() && (server.isReadyToServe())) {
                return (server);
            }

            // Next.
            server = null;
        }

        if (count >= 10) {
            log.warn("No available alive servers after 10 tries from load balancer: "
                    + lb);
        }
        return server;
    }

    /**
     * Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
     *
     * @param modulo The modulo to bound the value of the counter.
     * @return The next value.
     */
    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextServerCyclicCounter.get();
            int next = (current + 1) % modulo;
            if (nextServerCyclicCounter.compareAndSet(current, next))
                return next;
        }
    }

通过上面源码可以看出,负载均衡的算法为: rest接口的第几次请求数 % 服务器集群总数量 = 实际调用服务器的下标,每次服务重启,rest接口计数从1开始

比如: 8001,8002两台机器成为集群,List[0] serviceInstances = 127.0.0.1:8002 ,List[1] serviceInstances = 127.0.0.1:8001 服务器集群总数量为2

  • rest接口第一次请求时:1 % 2=1 ,得到服务器下标为1,则服务地址为127.0.0.1:8001
  • rest接口第二次请求时:2 % 2=0 ,得到服务器下标为0,则服务地址为127.0.0.1:8002
  • rest接口第三次请求时:3 % 2=1 ,得到服务器下标为1,则服务地址为127.0.0.1:8001
  • rest接口第四次请求时:4 % 2=0 ,得到服务器下标为0,则服务地址为127.0.0.1:8002
  • ..........

搞懂了这个,那我们自己实现一下轮询算法:

基础环境,还是使用之前的:

服务注册中心: cloud-eureka-server7001,cloud-eureka-server7002 在这里插入图片描述

服务提供者 cloud-provider-payment8001,cloud-provider-payment8002

在这里插入图片描述 服务消费者 cloud-consumer-order80 在这里插入图片描述

在服务提供者cloud-provider-payment8001,cloud-provider-payment8002 写个方法,简单返回下接口即可:

@GetMapping("/payment/getServer")
    public String getServer(){
        return this.serverPort;
    }

在服务消费者 cloud-consumer-order80,注释掉LoadBalanced注解,我们自己写负载均衡算法 在这里插入图片描述 至此,基础环境配置完成

手写轮询算法

1.在服务消费者里新建负载均衡策略接口

public interface LoadBalancer {
    /**
    * @Description  获取服务实例
    *
    */
    ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances);
}

2.编写实现类,实现核心逻辑

@Component
@Slf4j
public class PollLoadBalancer implements LoadBalancer {

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int incrementAndGet() {
        int current;
        int next;
        do {
            current = atomicInteger.get();
            //防溢出
            next = current >= Integer.MAX_VALUE ? 0 : current + 1;
        } while (!atomicInteger.compareAndSet(current, next));
        log.info("===================第几次请求:"+next);
        return next;
    }

    @Override
    public ServiceInstance getServiceInstance(List<ServiceInstance> serviceInstances) {
        // rest接口的第几次请求数 % 服务器集群总数量=index
        int index = incrementAndGet() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

3.控制器

@RestController
public class OrderController {
    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private LoadBalancer loadBalancer;

    @GetMapping("/consumer/payment/pollLb")
    public String PollLb(){
        List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-SERVICE");
        if (instances == null || instances.size() <= 0){
            return null;
        }
        ServiceInstance instance = loadBalancer.getServiceInstance(instances);
        URI uri = instance.getUri();
        return restTemplate.getForObject(uri + "/payment/getServer", String.class);
    }
}

效果如下,可以看出,我们自定义的轮询策略实现成功 在这里插入图片描述