SpringCloud —— Ribbon 负载均衡算法

前文

SpringCloud 简介

SpringCloud 版本选型

SpringCloud 工程构建

SpringCloud —— Eureka 注册中心

SpringCloud —— Eureka 集群

SpringCloud —— 服务注册进 Eureka 集群

SpringCloud —— Eureka 自我保护

SpringCloud —— SpringCloud Consul 实现服务注册中心

SpringCloud —— 三个注册中心的异同点

SpringCloud —— Ribbon

Ribbon 负载均衡原理

负载均衡算法:REST 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务启动后 REST 接口计数从 1 开始

List<ServiceInstance> instances = discoveryClient.getInstances("DEMO-PROVIDER-PAYMENT");

如:
List[0] instances = 127.0.0.1:8001
List[1] instances = 127.0.0.1:8002

1 % 2 = 1 ——》index = 1 list.get(index)
2 % 2 = 0 ——》index = 0 list.get(index)
3 % 2 = 1 ——》index = 1 list.get(index)
4 % 2 = 0 ——》index = 0 list.get(index)
5 % 2 = 1 ——》index = 1 list.get(index)

8001 + 8002 组合成为集群,共计两台机器,集群总数为 2,按照轮询算法原理:

当总请求数为 1 时:1 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002
当总请求数为 1 时:2 % 2 = 0 对应下标位置为 0,则获得服务地址为 127.0.0.1:8001
当总请求数为 1 时:3 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002
当总请求数为 1 时:4 % 2 = 0 对应下标位置为 0,则获得服务地址为 127.0.0.1:8001
当总请求数为 1 时:5 % 2 = 1 对应下标位置为 1,则获得服务地址为 127.0.0.1:8002

以此类推…

Ribbon 源码分析

先来看下 IRule 的 UML 图
在这里插入图片描述
下面是 IRule 的接口源码
在这里插入图片描述
IRule 的实现类
在这里插入图片描述

RoundRobinRule

来看下默认的轮询算法源码 RoundRobinRule.java

可以发现 RoundRobinRule 类中定义了一个 AtomicInteger, 并初始化为 0
在这里插入图片描述
再来看下 RoundRobinRule 实现 IRule 的 choose 方法

public Server choose(ILoadBalancer lb, Object key) {
	// 如果 lb 等于 null,也就是说没有负载均衡
    if (lb == null) {
        log.warn("no load balancer");
        // 返回 null
        return null;
    }

    Server server = null;
    int count = 0;
    // 如果服务器为 null 并且 count++ < 10
    while (server == null && count++ < 10) {
    	// 获取可达(健康的)的机器(服务器)
        List<Server> reachableServers = lb.getReachableServers();
        // 获取所有机器
        List<Server> allServers = lb.getAllServers();
        // 获取健康服务的数量
        int upCount = reachableServers.size();
        // 获取所有服务的数量,这里也就是上面所说的获取集群总数量如 8001 + 8002 两台机器
        int serverCount = allServers.size();

		// 一般只要有服务启动成功,通常不会执行这个 if
        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

		// 这里得到的值为 1,为什么是 1,下面会讲到
        int nextServerIndex = incrementAndGetModulo(serverCount);
        // 获取下一个服务下标,如 0、1、0、1
        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;
}

getReachableServers()

返回可访问的服务器
在这里插入图片描述

getAllServers()

返回所有已知的服务器,包括可访问的和不可访问的
在这里插入图片描述

incrementAndGetModulo

// 根据上面的代码如果集群数量为 2,那么这里 modulo 传的就是 2
private int incrementAndGetModulo(int modulo) {
	// 以下是一个自旋锁
    for (;;) {
        // nextServerCyclicCounter 上面已经介绍过,初始值为 0,那么也就是说 current = 0
        int current = nextServerCyclicCounter.get();
        // (0 + 1)% 2 = 1,根据上面提到的负载均衡原理,这里的下标就是 1
        int next = (current + 1) % modulo;
        // 这里使用的是 CAS (比较并交换),将当前的值(current)传进去,下一次的值传进去
        // 假设 next 值没修改过的话,那么 next 值就是 1,否则开始自旋,不断比较
        if (nextServerCyclicCounter.compareAndSet(current, next))
        	// 返回 next 值
            return next;
    }
}

自定义轮询算法

先定义一个接口

package com.java.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import java.util.List;

/**
 * @author Woo_home
 * @create 2020/3/27 13:13
 */

public interface LoadBalancer {
    ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

创建实现类

package com.java.springcloud.lb;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Woo_home
 * @create 2020/3/27 13:14
 */

@Component // 一定要加上这个注解,否则无法扫描
public class MyLB implements LoadBalancer{

    private AtomicInteger atomicInteger = new AtomicInteger(0);

    public final int getAndIncrement() {
        int current;
        int next;
        do {
            current = this.atomicInteger.get();
            // 这一串数字是 Integer.MAX_VALUE 的值
            next = current >= 2147483647 ? 0 : current + 1;
        } while (!this.atomicInteger.compareAndSet(current,next)); // 这里不要忘记加上 !,不然打印的结果出乎你的意料
        System.out.println("****** 第 " + next + " 次访问 ****** : ");
        // 返回的是第几次访问
        return next;
    }

    // 负载均衡算法:REST 接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务启动后 REST 接口计数从 1 开始
    @Override
    public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
        int index = getAndIncrement() % serviceInstances.size();
        return serviceInstances.get(index);
    }
}

修改消费者端 Controller

@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB() {
	// 获取服务实例
    List<ServiceInstance> instances = discoveryClient.getInstances("DEMO-PROVIDER-PAYMENT");
    if (instances == null || instances.size() <= 0) {
        return null;
    }

	// 使用自定义的轮询算法
    ServiceInstance serviceInstance = loadBalancer.instances(instances);
    URI uri = serviceInstance.getUri();

    return restTemplate.getForObject(uri + "/payment/lb",String.class);
}

访问页面
在这里插入图片描述
在这里插入图片描述
控制台输出
在这里插入图片描述

完整代码已上传到码云,感兴趣的朋友可以下载测试下 完整代码

发布了227 篇原创文章 · 获赞 1032 · 访问量 24万+

猜你喜欢

转载自blog.csdn.net/Woo_home/article/details/105137768